Skip to content

Commit bdbf362

Browse files
Allow calling COM methods/getters requirering hybrid calling (METHOD+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.
1 parent 1b89511 commit bdbf362

File tree

5 files changed

+225
-5
lines changed

5 files changed

+225
-5
lines changed

contrib/platform/src/com/sun/jna/platform/win32/COM/COMBindingBaseObject.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,34 @@ protected HRESULT oleMethod(int nType, VARIANT.ByReference pvResult,
262262
dp.write();
263263
}
264264

265+
// Apply "fix" according to
266+
// https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/
267+
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx
268+
//
269+
// This was found when trying to bind InchesToPoints from the _Application
270+
// dispatch interface of the MS Word 15 type library
271+
//
272+
// The signature according the ITypeLib Viewer (OLE/COM Object Viewer):
273+
// [id(0x00000172), helpcontext(0x09700172)]
274+
// single InchesToPoints([in] single Inches);
275+
//
276+
// This matches common sense: it is a "normal" method call that
277+
// does the conversion.
278+
//
279+
// Invoking this with only either OleAuto.DISPATCH_METHOD or
280+
// OleAuto.DISPATCH_PROPERTYGET set does not work.
281+
//
282+
// Only if both flags are set the call works
283+
final int finalNType;
284+
if (nType == OleAuto.DISPATCH_METHOD || nType == OleAuto.DISPATCH_PROPERTYGET) {
285+
finalNType = OleAuto.DISPATCH_METHOD | OleAuto.DISPATCH_PROPERTYGET;
286+
} else {
287+
finalNType = nType;
288+
}
289+
265290
// Make the call!
266291
HRESULT hr = pDisp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT,
267-
new WinDef.WORD(nType), dp, pvResult, pExcepInfo, puArgErr);
292+
new WinDef.WORD(finalNType), dp, pvResult, pExcepInfo, puArgErr);
268293

269294
COMUtils.checkRC(hr, pExcepInfo, puArgErr);
270295
return hr;

contrib/platform/src/com/sun/jna/platform/win32/COM/util/ProxyObject.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,31 @@ protected HRESULT oleMethod(final int nType, final VARIANT.ByReference pvResult,
611611
dp.cNamedArgs = new UINT(_argsLen);
612612
dp.rgdispidNamedArgs = new DISPIDByReference(OaIdl.DISPID_PROPERTYPUT);
613613
}
614+
615+
// Apply "fix" according to
616+
// https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/
617+
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx
618+
//
619+
// This was found when trying to bind InchesToPoints from the
620+
// _Application dispatch interface of the MS Word 15 type library
621+
//
622+
// The signature according the ITypeLib Viewer (OLE/COM Object Viewer):
623+
// [id(0x00000172), helpcontext(0x09700172)]
624+
// single InchesToPoints([in] single Inches);
625+
//
626+
// This matches common sense: it is a "normal" method call that
627+
// does the conversion.
628+
//
629+
// Invoking this with only either OleAuto.DISPATCH_METHOD or
630+
// OleAuto.DISPATCH_PROPERTYGET set does not work.
631+
//
632+
// Only if both flags are set the call works
633+
final int finalNType;
634+
if(nType == OleAuto.DISPATCH_METHOD || nType == OleAuto.DISPATCH_PROPERTYGET) {
635+
finalNType = OleAuto.DISPATCH_METHOD | OleAuto.DISPATCH_PROPERTYGET;
636+
} else {
637+
finalNType = nType;
638+
}
614639

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

contrib/platform/test/com/sun/jna/platform/win32/COM/IDispatchTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private Dispatch createIDispatch() {
3939
try {
4040
PointerByReference pDispatch = new PointerByReference();
4141

42-
// Get CLSID for Word.Application...
42+
// Get CLSID for Shell.Application...
4343
CLSID.ByReference clsid = new CLSID.ByReference();
4444
HRESULT hr = Ole32.INSTANCE.CLSIDFromProgID("Shell.Application",
4545
clsid);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package com.sun.jna.platform.win32.COM.util;
2+
3+
import com.sun.jna.platform.win32.COM.COMException;
4+
import com.sun.jna.platform.win32.COM.COMLateBindingObject;
5+
import com.sun.jna.platform.win32.COM.COMUtils;
6+
import com.sun.jna.platform.win32.COM.Dispatch;
7+
import com.sun.jna.platform.win32.COM.util.annotation.ComInterface;
8+
import com.sun.jna.platform.win32.COM.util.annotation.ComMethod;
9+
import com.sun.jna.platform.win32.COM.util.annotation.ComObject;
10+
import com.sun.jna.platform.win32.Guid;
11+
import com.sun.jna.platform.win32.Guid.CLSID;
12+
import com.sun.jna.platform.win32.Guid.GUID;
13+
import com.sun.jna.platform.win32.Guid.IID;
14+
import com.sun.jna.platform.win32.Guid.REFIID;
15+
import com.sun.jna.platform.win32.Kernel32;
16+
import com.sun.jna.platform.win32.OaIdl;
17+
import com.sun.jna.platform.win32.OaIdl.DISPID;
18+
import com.sun.jna.platform.win32.Ole32;
19+
import com.sun.jna.platform.win32.OleAuto;
20+
import com.sun.jna.platform.win32.Variant;
21+
import com.sun.jna.platform.win32.Variant.VARIANT;
22+
import com.sun.jna.platform.win32.WTypes;
23+
import com.sun.jna.platform.win32.WinDef;
24+
import com.sun.jna.platform.win32.WinDef.UINT;
25+
import com.sun.jna.platform.win32.WinDef.WORD;
26+
import com.sun.jna.platform.win32.WinNT.HRESULT;
27+
import com.sun.jna.ptr.IntByReference;
28+
import com.sun.jna.ptr.PointerByReference;
29+
import java.util.logging.Level;
30+
import java.util.logging.Logger;
31+
import junit.framework.TestCase;
32+
import org.junit.Test;
33+
import static junit.framework.TestCase.assertEquals;
34+
import static junit.framework.TestCase.assertTrue;
35+
36+
/**
37+
* In the word COM bindings it was determined, that some methods can't be called
38+
* with only wFlags OleAuto.DISPATCH_METHOD or OleAuto.DISPATCH_PROPERTYGET.
39+
*
40+
* For these methods both flags need to be set.
41+
*
42+
* https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/
43+
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx
44+
*
45+
* A sample function is InchesToPoints from thw word typelibrary
46+
*/
47+
public class HybdridCOMInvocationTest extends TestCase {
48+
49+
private static final Logger LOG = Logger.getLogger(HybdridCOMInvocationTest.class.getName());
50+
51+
private static final String CLSID_WORD_STRING = "{000209FF-0000-0000-C000-000000000046}";
52+
private static final String IID_APPLICATION_STRING = "{00020970-0000-0000-C000-000000000046}";
53+
private static final GUID CLSID_WORD = new GUID(CLSID_WORD_STRING);
54+
private static final IID IID_APPLICATION = new IID(new GUID(IID_APPLICATION_STRING));
55+
56+
@Override
57+
protected void tearDown() throws Exception {
58+
super.tearDown();
59+
Ole32.INSTANCE.CoUninitialize();
60+
}
61+
62+
@Override
63+
protected void setUp() throws Exception {
64+
// Initialize COM for this thread...
65+
HRESULT hr = Ole32.INSTANCE.CoInitialize(null);
66+
}
67+
68+
@Test
69+
public void testOfficeInvocationProblemCOMUtil() {
70+
Factory fact = new Factory();
71+
Application app;
72+
try {
73+
app = fact.createObject(Application.class);
74+
} catch (COMException ex) {
75+
LOG.log(Level.INFO, "HybdridCOMInvocationTest test was not run, MS Word object could not be instantiated.", ex);
76+
return;
77+
}
78+
// If this fails: remember: floats are not exact, if this happens replace
79+
// with a range check
80+
assertEquals(72.0f, app.InchesToPoints(1F));
81+
}
82+
83+
@Test
84+
public void testOfficeInvocationProblemCOMBindingObject() {
85+
WordApplication app;
86+
try {
87+
app = new WordApplication(false);
88+
} catch (COMException ex) {
89+
LOG.log(Level.INFO, "HybdridCOMInvocationTest test was not run, MS Word object could not be instantiated.", ex);
90+
return;
91+
}
92+
assertEquals(72.0f, app.InchesToPoints(1F));
93+
}
94+
95+
96+
public void testOfficeInvocationDemonstration() {
97+
// THIS IS NOT A TEST
98+
//
99+
// This reproduces the problem by using the dispatch directly.
100+
101+
PointerByReference pDispatch = new PointerByReference();
102+
103+
HRESULT hr = Ole32.INSTANCE.CoCreateInstance(CLSID_WORD, null,
104+
WTypes.CLSCTX_SERVER, IID_APPLICATION, pDispatch);
105+
106+
if(! COMUtils.SUCCEEDED(hr)) {
107+
LOG.log(Level.INFO, "HybdridCOMInvocationTest test was not run, MS Word object could not be instantiated.");
108+
return;
109+
}
110+
111+
Dispatch dp = new Dispatch(pDispatch.getValue());
112+
113+
// DispID of InchesToPoints
114+
DISPID dispId = new OaIdl.DISPID(0x00000172);
115+
// Interface _Application of MS Word type library
116+
WinDef.LCID LOCALE_SYSTEM_DEFAULT = Kernel32.INSTANCE.GetSystemDefaultLCID();
117+
Variant.VARIANT.ByReference result = new Variant.VARIANT.ByReference();
118+
OaIdl.EXCEPINFO.ByReference pExcepInfo = new OaIdl.EXCEPINFO.ByReference();
119+
IntByReference puArgErr = new IntByReference();
120+
121+
WORD wFlagsMethod = new WinDef.WORD(OleAuto.DISPATCH_METHOD);
122+
WORD wFlagsGet = new WinDef.WORD(OleAuto.DISPATCH_PROPERTYGET);
123+
WORD wFlagsCombined = new WinDef.WORD(OleAuto.DISPATCH_METHOD | OleAuto.DISPATCH_PROPERTYGET);
124+
125+
OleAuto.DISPPARAMS.ByReference pDispParams = new OleAuto.DISPPARAMS.ByReference();
126+
VARIANT[] params = new VARIANT[1];
127+
params[0] = new VARIANT(1f);
128+
pDispParams.cArgs = new UINT(1);
129+
pDispParams.cNamedArgs = new UINT(0);
130+
pDispParams.rgvarg = new Variant.VariantArg.ByReference(params);
131+
pDispParams.rgdispidNamedArgs = new OaIdl.DISPIDByReference();
132+
133+
// Call InchesToPoints as a method
134+
hr = dp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, wFlagsMethod, pDispParams, result, pExcepInfo, puArgErr);
135+
assertTrue(COMUtils.FAILED(hr));
136+
137+
// Call InchesToPoints as a property getter
138+
hr = dp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, wFlagsGet, pDispParams, result, pExcepInfo, puArgErr);
139+
assertTrue(COMUtils.FAILED(hr));
140+
141+
// Call InchesToPoints as a hybrid
142+
hr = dp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, wFlagsCombined, pDispParams, result, pExcepInfo, puArgErr);
143+
assertTrue(COMUtils.SUCCEEDED(hr));
144+
145+
assertEquals(72.0f, result.floatValue());
146+
}
147+
148+
@ComObject(clsId = CLSID_WORD_STRING)
149+
public static interface Application extends IDispatch, _Application {
150+
}
151+
152+
@ComInterface(iid = IID_APPLICATION_STRING)
153+
public static interface _Application {
154+
@ComMethod
155+
Float InchesToPoints(Float value);
156+
}
157+
158+
public static class WordApplication extends COMLateBindingObject {
159+
160+
public WordApplication(boolean useActiveInstance) {
161+
super(new CLSID(CLSID_WORD), useActiveInstance);
162+
}
163+
164+
public Float InchesToPoints(Float value) {
165+
VARIANT.ByReference pvResult = new VARIANT.ByReference();
166+
this.oleMethod(OleAuto.DISPATCH_METHOD , pvResult, this.getIDispatch(), "InchesToPoints", new VARIANT[] {new VARIANT(value)});
167+
return pvResult.floatValue();
168+
}
169+
}
170+
}

contrib/platform/test/com/sun/jna/platform/win32/COM/util/RunningObjectTable_Test.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ public class RunningObjectTable_Test {
3131
@ComInterface(iid="{00020970-0000-0000-C000-000000000046}")
3232
interface Application extends IUnknown {
3333
@ComProperty
34-
boolean getVisible();
34+
Boolean getVisible();
3535

3636
@ComProperty
37-
void setVisible(boolean value);
37+
void setVisible(Boolean value);
3838

3939
@ComMethod
4040
void Quit(boolean SaveChanges, Object OriginalFormat, Boolean RouteDocument);

0 commit comments

Comments
 (0)