4

The following code fails with an OLE 800040005 "unspecified" error on the CentimetersToPoint call when executed in Delphi (XE), the similar VBS or VBA version passes

var w : OleVariant;

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

FWIW the type library gives

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

By default, Delphi only passes the floating values as Double, so I tried calling IDispatch.Invoke directly and passing the argument as VT_R4, but without better results.

edit: VB version that works (save to .vbs)

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

Any other suggestions of what could be going wrong?

Eric Grange
  • 5,931
  • 1
  • 39
  • 61
  • did u tried TWordApplication component for Office version matching the actually installed one (Delphi xe2 has three versions in box). Maybe you think you missed one or two empty parameters, like LCID. Early binding would give you compile-time checking, rather than deferred runtime check – Arioch 'The Apr 29 '13 at 13:21
  • TWordApplication isn't applicable, this is actually a simplified and more easily reproduceable version of the issue. Also the similar code works in VB. – Eric Grange Apr 29 '13 at 13:24
  • You cannot be sure VBA eve nword via COM, and you even less sure it can doies work via COM-to-OLE gateway. TWordApplication is good attempt to debug this, why you claim it cannot be used ? This is not simplified case, but a different program, working via different interface and lacking things like type safety. A bit too much for "minimization" – Arioch 'The Apr 29 '13 at 13:30
  • Place the same code as above in a .VBS with CreateObject(), it will use OLE, and work. TWordApplication isn't relevant here because it's COM, the issue here is with OLE, and it's a minimization because the original issue came with many more lines of code involving OLE, and you only need Word to reproduce it with the above snippet. – Eric Grange Apr 29 '13 at 13:34
  • Error 80004005 is the E_FAIL. Have you tried getting more information on what is going wrong? Msdn page on most common COM errors: "call the API function GetErrorInfo to retrieve the most recently set IErrorInfo pointer in the current logical thread. The GetDescription method of that interface may return a text description of the error." See [Standard COM errors](http://msdn.microsoft.com/en-us/library/ee824990(v=cs.10).aspx). – Marjan Venema Apr 29 '13 at 14:13
  • GetErrorInfo description is an empty string (same for the exceptInfo structure when manually invoking through IDispatch.Invoke) – Eric Grange Apr 29 '13 at 14:38
  • possible duplicate of [How do I call functions that receive floating point values using IDispatch.Invoke?](http://stackoverflow.com/questions/16285138/how-do-i-call-functions-that-receive-floating-point-values-using-idispatch-invok) – David Heffernan Apr 29 '13 at 20:07
  • 1
    Against the DUPLICATE: the Del[phi developers would likely search for delphi-tagged questions and would miss that. Also i wonder if this call sequence can be patched in RTL: http://pastebin.ca/2369858 - it is not over-looked guard, like with signed integers in Xe2u4 and XE3, it is intentionally added guard. Removing it would fix this call, but potentially may break other calls instead (which ones?) ! – Arioch 'The Apr 30 '13 at 08:34
  • just for the record, i patched this in XE2 Win32 RTL in runtime. – Arioch 'The May 07 '13 at 10:18

1 Answers1

3

I initially suspected that the issue is that the function expects Single and Delphi converts your float to something else. When I tracked it down in the debugger I find that the variant being passed to Invoke is has VType of varCurrency and a currency value of 2. Quite how that happens I'm not sure!

As I discovered, answering this question, it's surprisingly tricky to get a single precision float into a variant. I initially suspected that you can use the solution I presented there to solve your problem.

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

But this fails also, in the same way, for reasons I don't yet understand.

Like you, I tried calling the function using IDispatch.Invoke. This is what I came up with:

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.

I cannot call CentimetersToPoints this way, but can call the ProductCode function.

To add to your collection of success/failure indicators, when I call CentimetersToPoints function using PowerShell I have success. When I call using Python's win32com.client, I get E_FAIL.

There is clearly some special magic ingredient that we are missing. It seems that all the MS tools know about this magic.

I conclude that it is not possible to call CentimetersToPoints using variant dispatch as implemented in Delphi. It does not know about the magic, whatever that magic is.

It is clearly possible to call Invoke on the IDispatch and succeed. We can tell that because other environments manage to do so. So, what I do not know yet is what the missing magic is.

If you could use early bound COM, then you could sidestep this issue:

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

OK, with the help of Hans Passant, I have some Delphi code that manages to call this function:

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.

For reasons unknown, you need to include DISPATCH_PROPERTYGET as well as DISPATCH_METHOD.

The question that I asked probably makes this question a duplicate. So, I'm voting to close.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Your snippet gives the same error (and as I wrote, I noticed the issue and tried to work around it in the same way). – Eric Grange Apr 29 '13 at 13:36
  • It's weird, Single has to be the issue in some way, the same method in Excel works, though it accepts a double instead of a single... – Eric Grange Apr 29 '13 at 14:31
  • Btw, I tried the IDisptach.Invoke with hard-coded single, and I'm getting the same error as well. There has to be something else going on, as VBS manages to make the call successfully. – Eric Grange Apr 29 '13 at 14:33
  • I've been digging at this for a while now, with no success. It's really weird. I get to the point where the call to `Dispatch` is being made and even if I manually force `vt` to `4` and `fltVal` to `2.0`, I still see failure. Which makes no sense to me. – David Heffernan Apr 29 '13 at 14:36
  • I'm at the same point. If VBS wasn't successful, I would assume a bug in the OLE implementation... – Eric Grange Apr 29 '13 at 14:47
  • What are your options. Is early bound feasible? Or do you need to use late bound dispatch? – David Heffernan Apr 29 '13 at 15:27
  • It's ultimately for use in arbitrary scripting, so late bound is the the goal. – Eric Grange Apr 29 '13 at 15:48
  • Then I guess you'll have to give up on Delphi's dispatch implementation. Irrespective of whether the "bug" is in Delphi code, or in COM, I can't see any solution other than calling `IDispatch.Invoke` yourself. I guess we just need to work out how to do that. – David Heffernan Apr 29 '13 at 15:53
  • Same error when trying from Python using win32com.client. But works fine from PowerShell. Looks like some special trick is needed in the IDispatch.Invoke call. And only MS know the magic! Grr, tearing my hair out. – David Heffernan Apr 29 '13 at 16:15
  • 1
    @EricGrange I don't know whether or not I'm helping, but I have at least put together an argument that you cannot do this without using `Invoke`. I expect you've got code much the same as in my answer. It would have saved time if that code had been in the question. – David Heffernan Apr 29 '13 at 16:48
  • `CComPtr` is a failure too. This is beyond weird! – David Heffernan Apr 29 '13 at 17:23
  • 1
    @EricGrange Well, success of sorts. – David Heffernan Apr 29 '13 at 20:09
  • Perfect for my purpose, I can work with that. Thanks for the effort! That was a really tricky one. – Eric Grange Apr 30 '13 at 03:38
  • don't think this question should be closed as DUP - the linked question is nice, but it would not be found when searched for Delhpi issues, and would probably be overlooked. BTW, is there difference between Word's and Excel's methods ? for Excel's similar function "just works" – Arioch 'The Apr 30 '13 at 07:23
  • @RRuz please my cmt at http://stackoverflow.com/questions/16285138/ answer, and http://pastebin.ca/2369858 and http://msdn.microsoft.com/en-us/library/windows/desktop/ms221486.aspx - perhaps the ArgCount==0 guard is redundant and should be removed from RTL ? Perhaps ticket should be open in QC and FPC Mantis ? ....and in-memory patch made for pre-XE5 Delphi releases – Arioch 'The Apr 30 '13 at 10:02
  • Fix for Win32 Delphi XE2u4hf1: https://github.com/the-Arioch/XE2fixes http://qc.embarcadero.com/wc/qcmain.aspx?d=115373 – Arioch 'The Apr 30 '13 at 12:42
  • @RRuz could you please participate in the FPC tracker, link is above ? – Arioch 'The Aug 21 '13 at 14:35