Skip to content

Commit

Permalink
Allow calling COM methods/getters requirering hybrid calling (METHOD+…
Browse files Browse the repository at this point in the history
…PROPERTYGET convention)

It was found, that calling InchesToPoints method from word type library
failed. Research lead to 

https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/
https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx

Summary: there are methods in the word typelibrary that require both
PROPERTYGET _and_ METHOD to be set. With only one of these set the call
fails.

The article from delphitools argues, that automation compatible libraries
need to be compatible with VisualBasic which does not distingish methods
and property getters and will set both flags always.

The MSDN article advises this behaviour: "[...] Some languages cannot 
distinguish between retrieving a property and calling a method. In this 
case, you should set the flags DISPATCH_PROPERTYGET and DISPATCH_METHOD.
[...]"

This further support the advised way from delphitools and was implemented.
  • Loading branch information
matthiasblaesing committed Feb 14, 2016
1 parent 1b89511 commit bdbf362
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,34 @@ protected HRESULT oleMethod(int nType, VARIANT.ByReference pvResult,
dp.write();
}

// Apply "fix" according to
// https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx
//
// This was found when trying to bind InchesToPoints from the _Application
// dispatch interface of the MS Word 15 type library
//
// The signature according the ITypeLib Viewer (OLE/COM Object Viewer):
// [id(0x00000172), helpcontext(0x09700172)]
// single InchesToPoints([in] single Inches);
//
// This matches common sense: it is a "normal" method call that
// does the conversion.
//
// Invoking this with only either OleAuto.DISPATCH_METHOD or
// OleAuto.DISPATCH_PROPERTYGET set does not work.
//
// Only if both flags are set the call works
final int finalNType;
if (nType == OleAuto.DISPATCH_METHOD || nType == OleAuto.DISPATCH_PROPERTYGET) {
finalNType = OleAuto.DISPATCH_METHOD | OleAuto.DISPATCH_PROPERTYGET;
} else {
finalNType = nType;
}

// Make the call!
HRESULT hr = pDisp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT,
new WinDef.WORD(nType), dp, pvResult, pExcepInfo, puArgErr);
new WinDef.WORD(finalNType), dp, pvResult, pExcepInfo, puArgErr);

COMUtils.checkRC(hr, pExcepInfo, puArgErr);
return hr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,31 @@ protected HRESULT oleMethod(final int nType, final VARIANT.ByReference pvResult,
dp.cNamedArgs = new UINT(_argsLen);
dp.rgdispidNamedArgs = new DISPIDByReference(OaIdl.DISPID_PROPERTYPUT);
}

// Apply "fix" according to
// https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx
//
// This was found when trying to bind InchesToPoints from the
// _Application dispatch interface of the MS Word 15 type library
//
// The signature according the ITypeLib Viewer (OLE/COM Object Viewer):
// [id(0x00000172), helpcontext(0x09700172)]
// single InchesToPoints([in] single Inches);
//
// This matches common sense: it is a "normal" method call that
// does the conversion.
//
// Invoking this with only either OleAuto.DISPATCH_METHOD or
// OleAuto.DISPATCH_PROPERTYGET set does not work.
//
// Only if both flags are set the call works
final int finalNType;
if(nType == OleAuto.DISPATCH_METHOD || nType == OleAuto.DISPATCH_PROPERTYGET) {
finalNType = OleAuto.DISPATCH_METHOD | OleAuto.DISPATCH_PROPERTYGET;
} else {
finalNType = nType;
}

// Build DISPPARAMS
if (_argsLen > 0) {
Expand All @@ -629,7 +654,7 @@ protected HRESULT oleMethod(final int nType, final VARIANT.ByReference pvResult,
@Override
public HRESULT call() throws Exception {
return pDisp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT,
new WinDef.WORD(nType), dp, pvResult, pExcepInfo, puArgErr);
new WinDef.WORD(finalNType), dp, pvResult, pExcepInfo, puArgErr);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private Dispatch createIDispatch() {
try {
PointerByReference pDispatch = new PointerByReference();

// Get CLSID for Word.Application...
// Get CLSID for Shell.Application...
CLSID.ByReference clsid = new CLSID.ByReference();
HRESULT hr = Ole32.INSTANCE.CLSIDFromProgID("Shell.Application",
clsid);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package com.sun.jna.platform.win32.COM.util;

import com.sun.jna.platform.win32.COM.COMException;
import com.sun.jna.platform.win32.COM.COMLateBindingObject;
import com.sun.jna.platform.win32.COM.COMUtils;
import com.sun.jna.platform.win32.COM.Dispatch;
import com.sun.jna.platform.win32.COM.util.annotation.ComInterface;
import com.sun.jna.platform.win32.COM.util.annotation.ComMethod;
import com.sun.jna.platform.win32.COM.util.annotation.ComObject;
import com.sun.jna.platform.win32.Guid;
import com.sun.jna.platform.win32.Guid.CLSID;
import com.sun.jna.platform.win32.Guid.GUID;
import com.sun.jna.platform.win32.Guid.IID;
import com.sun.jna.platform.win32.Guid.REFIID;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.OaIdl;
import com.sun.jna.platform.win32.OaIdl.DISPID;
import com.sun.jna.platform.win32.Ole32;
import com.sun.jna.platform.win32.OleAuto;
import com.sun.jna.platform.win32.Variant;
import com.sun.jna.platform.win32.Variant.VARIANT;
import com.sun.jna.platform.win32.WTypes;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinDef.UINT;
import com.sun.jna.platform.win32.WinDef.WORD;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import junit.framework.TestCase;
import org.junit.Test;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;

/**
* In the word COM bindings it was determined, that some methods can't be called
* with only wFlags OleAuto.DISPATCH_METHOD or OleAuto.DISPATCH_PROPERTYGET.
*
* For these methods both flags need to be set.
*
* https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx
*
* A sample function is InchesToPoints from thw word typelibrary
*/
public class HybdridCOMInvocationTest extends TestCase {

private static final Logger LOG = Logger.getLogger(HybdridCOMInvocationTest.class.getName());

private static final String CLSID_WORD_STRING = "{000209FF-0000-0000-C000-000000000046}";
private static final String IID_APPLICATION_STRING = "{00020970-0000-0000-C000-000000000046}";
private static final GUID CLSID_WORD = new GUID(CLSID_WORD_STRING);
private static final IID IID_APPLICATION = new IID(new GUID(IID_APPLICATION_STRING));

@Override
protected void tearDown() throws Exception {
super.tearDown();
Ole32.INSTANCE.CoUninitialize();
}

@Override
protected void setUp() throws Exception {
// Initialize COM for this thread...
HRESULT hr = Ole32.INSTANCE.CoInitialize(null);
}

@Test
public void testOfficeInvocationProblemCOMUtil() {
Factory fact = new Factory();
Application app;
try {
app = fact.createObject(Application.class);
} catch (COMException ex) {
LOG.log(Level.INFO, "HybdridCOMInvocationTest test was not run, MS Word object could not be instantiated.", ex);
return;
}
// If this fails: remember: floats are not exact, if this happens replace
// with a range check
assertEquals(72.0f, app.InchesToPoints(1F));
}

@Test
public void testOfficeInvocationProblemCOMBindingObject() {
WordApplication app;
try {
app = new WordApplication(false);
} catch (COMException ex) {
LOG.log(Level.INFO, "HybdridCOMInvocationTest test was not run, MS Word object could not be instantiated.", ex);
return;
}
assertEquals(72.0f, app.InchesToPoints(1F));
}


public void testOfficeInvocationDemonstration() {
// THIS IS NOT A TEST
//
// This reproduces the problem by using the dispatch directly.

PointerByReference pDispatch = new PointerByReference();

HRESULT hr = Ole32.INSTANCE.CoCreateInstance(CLSID_WORD, null,
WTypes.CLSCTX_SERVER, IID_APPLICATION, pDispatch);

if(! COMUtils.SUCCEEDED(hr)) {
LOG.log(Level.INFO, "HybdridCOMInvocationTest test was not run, MS Word object could not be instantiated.");
return;
}

Dispatch dp = new Dispatch(pDispatch.getValue());

// DispID of InchesToPoints
DISPID dispId = new OaIdl.DISPID(0x00000172);
// Interface _Application of MS Word type library
WinDef.LCID LOCALE_SYSTEM_DEFAULT = Kernel32.INSTANCE.GetSystemDefaultLCID();
Variant.VARIANT.ByReference result = new Variant.VARIANT.ByReference();
OaIdl.EXCEPINFO.ByReference pExcepInfo = new OaIdl.EXCEPINFO.ByReference();
IntByReference puArgErr = new IntByReference();

WORD wFlagsMethod = new WinDef.WORD(OleAuto.DISPATCH_METHOD);
WORD wFlagsGet = new WinDef.WORD(OleAuto.DISPATCH_PROPERTYGET);
WORD wFlagsCombined = new WinDef.WORD(OleAuto.DISPATCH_METHOD | OleAuto.DISPATCH_PROPERTYGET);

OleAuto.DISPPARAMS.ByReference pDispParams = new OleAuto.DISPPARAMS.ByReference();
VARIANT[] params = new VARIANT[1];
params[0] = new VARIANT(1f);
pDispParams.cArgs = new UINT(1);
pDispParams.cNamedArgs = new UINT(0);
pDispParams.rgvarg = new Variant.VariantArg.ByReference(params);
pDispParams.rgdispidNamedArgs = new OaIdl.DISPIDByReference();

// Call InchesToPoints as a method
hr = dp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, wFlagsMethod, pDispParams, result, pExcepInfo, puArgErr);
assertTrue(COMUtils.FAILED(hr));

// Call InchesToPoints as a property getter
hr = dp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, wFlagsGet, pDispParams, result, pExcepInfo, puArgErr);
assertTrue(COMUtils.FAILED(hr));

// Call InchesToPoints as a hybrid
hr = dp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, wFlagsCombined, pDispParams, result, pExcepInfo, puArgErr);
assertTrue(COMUtils.SUCCEEDED(hr));

assertEquals(72.0f, result.floatValue());
}

@ComObject(clsId = CLSID_WORD_STRING)
public static interface Application extends IDispatch, _Application {
}

@ComInterface(iid = IID_APPLICATION_STRING)
public static interface _Application {
@ComMethod
Float InchesToPoints(Float value);
}

public static class WordApplication extends COMLateBindingObject {

public WordApplication(boolean useActiveInstance) {
super(new CLSID(CLSID_WORD), useActiveInstance);
}

public Float InchesToPoints(Float value) {
VARIANT.ByReference pvResult = new VARIANT.ByReference();
this.oleMethod(OleAuto.DISPATCH_METHOD , pvResult, this.getIDispatch(), "InchesToPoints", new VARIANT[] {new VARIANT(value)});
return pvResult.floatValue();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ public class RunningObjectTable_Test {
@ComInterface(iid="{00020970-0000-0000-C000-000000000046}")
interface Application extends IUnknown {
@ComProperty
boolean getVisible();
Boolean getVisible();

@ComProperty
void setVisible(boolean value);
void setVisible(Boolean value);

@ComMethod
void Quit(boolean SaveChanges, Object OriginalFormat, Boolean RouteDocument);
Expand Down

0 comments on commit bdbf362

Please sign in to comment.