Skip to content

Commit

Permalink
Merge pull request #595 from matthiasblaesing/com_hybrid_calling_conv…
Browse files Browse the repository at this point in the history
…ention

Allow calling COM methods/getters requirering hybrid calling (METHOD+PROPERTYGET convention)
  • Loading branch information
dblock committed Feb 16, 2016
2 parents 8cb0dae + 5ab9cd0 commit 0864d49
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Features
* [#569](https://github.com/java-native-access/jna/pull/569): Added `com.sun.jna.platform.win32.Winspool.PRINTER_INFO_2` support. Added GetPrinter and ClosePrinter functions in `com.sun.jna.platform.win32.Winspool` - [@IvanRF](https://github.com/IvanRF).
* [#583](https://github.com/java-native-access/jna/pull/583): Added printer attributes and status - [@IvanRF](https://github.com/IvanRF).
* [#589](https://github.com/java-native-access/jna/pull/589): Use MethodResultContext in direct mapping (as done in interface mapping) - [@marco2357](https://github.com/marco2357).
* [#595](https://github.com/java-native-access/jna/pull/595): Allow calling COM methods/getters requiring hybrid calling (METHOD+PROPERTYGET) - [@matthiasblaesing](https://github.com/matthiasblaesing).

Bug Fixes
---------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,40 @@ 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
//
// 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 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);

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,36 @@ 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
//
// 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 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);
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 +659,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 0864d49

Please sign in to comment.