Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nitpick, but maybe just say

if (nType == ) {
  nType = ...
}

It's consistent to checks above and we don't need the extra variable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work - in ProxyObject the invocation is dispatched into a separate thread and for that the invoke is wrapped into a Callable, that won't capture ntype if it is not final and if it is final, it can't be changed. so one more variable is needed.

The previous comparisons in the code don't modify ntype.

It would be possible to apply the requested change in COMBindingBaseObject, but that code is identical to the code in ProxyObject and intentionally so.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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