Skip to content

Commit

Permalink
Merge pull request #654 from matthiasblaesing/com_callback_named_params
Browse files Browse the repository at this point in the history
Implement named callback parameters for COM Callbacks
  • Loading branch information
matthiasblaesing committed May 12, 2016
2 parents 9c63dc4 + 68c5978 commit 071e2af
Show file tree
Hide file tree
Showing 8 changed files with 464 additions and 88 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Features
* [#642](https://github.com/java-native-access/jna/pull/642): COM calls with variable number of arguments (varargs) are now supported - [@SevenOf9Sleeper](https://github.com/SevenOf9Sleeper).
* [#644](https://github.com/java-native-access/jna/pull/644): New ant target 'install' for installing JNA artifacts in local m2-repository - [@SevenOf9Sleeper](https://github.com/SevenOf9Sleeper).
* [#649](https://github.com/java-native-access/jna/pull/649): Bugfix msoffice sample and add two samples taken from MSDN and translated from VisualBasic to Java - [@matthiasblaesing](https://github.com/matthiasblaesing).
* [#654](https://github.com/java-native-access/jna/pull/654): Support named arguments for `com.sun.jna.platform.win32.COM.util.CallbackProxy` based callbacks - [@matthiasblaesing](https://github.com/matthiasblaesing).

Bug Fixes
---------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,12 @@ protected HRESULT oleMethod(int nType, VARIANT.ByReference pvResult,

// Handle special-case for property-puts!
if (nType == OleAuto.DISPATCH_PROPERTYPUT) {
dp.cNamedArgs = new UINT(_argsLen);
dp.rgdispidNamedArgs = new DISPIDByReference(
OaIdl.DISPID_PROPERTYPUT);
dp.setRgdispidNamedArgs(new DISPID[] {OaIdl.DISPID_PROPERTYPUT});
}

// Build DISPPARAMS
if (_argsLen > 0) {
dp.cArgs = new UINT(_args.length);
// make pointer of variant array
dp.rgvarg = new VariantArg.ByReference(_args);
dp.setArgs(_args);

// write 'DISPPARAMS' structure to memory
dp.write();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.Guid;
import com.sun.jna.platform.win32.Guid.IID;
import com.sun.jna.platform.win32.Guid.REFIID;
import com.sun.jna.platform.win32.OaIdl.DISPID;
Expand All @@ -29,7 +28,6 @@
import com.sun.jna.platform.win32.OleAuto.DISPPARAMS;
import com.sun.jna.platform.win32.Variant;
import com.sun.jna.platform.win32.Variant.VARIANT;
import com.sun.jna.platform.win32.Variant.VariantArg;
import com.sun.jna.platform.win32.WinDef.LCID;
import com.sun.jna.platform.win32.WinDef.UINT;
import com.sun.jna.platform.win32.WinDef.UINTByReference;
Expand All @@ -40,7 +38,6 @@
import com.sun.jna.platform.win32.COM.COMUtils;
import com.sun.jna.platform.win32.COM.Dispatch;
import com.sun.jna.platform.win32.COM.DispatchListener;
import com.sun.jna.platform.win32.COM.IDispatch;
import com.sun.jna.platform.win32.COM.IDispatchCallback;
import com.sun.jna.platform.win32.COM.Unknown;
import com.sun.jna.platform.win32.COM.util.annotation.ComEventCallback;
Expand All @@ -49,7 +46,15 @@
import com.sun.jna.ptr.PointerByReference;

public class CallbackProxy implements IDispatchCallback {

// Helper declarations, initialized to default values by jvm
private static boolean DEFAULT_BOOLEAN;
private static byte DEFAULT_BYTE;
private static short DEFAULT_SHORT;
private static int DEFAULT_INT;
private static long DEFAULT_LONG;
private static float DEFAULT_FLOAT;
private static double DEFAULT_DOUBLE;

public CallbackProxy(Factory factory, Class<?> comEventCallbackInterface,
IComEventCallbackListener comEventCallbackListener) {
this.factory = factory;
Expand Down Expand Up @@ -90,6 +95,11 @@ Map<DISPID, Method> createDispIdMap(Class<?> comEventCallbackInterface) {
if (-1 == dispId) {
dispId = this.fetchDispIdFromName(annotation);
}
if(dispId == -1) {
CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent(
"DISPID for " + meth.getName() + " not found",
null);
}
map.put(new DISPID(dispId), meth);
}
}
Expand All @@ -105,50 +115,105 @@ int fetchDispIdFromName(ComEventCallback annotation) {
void invokeOnThread(final DISPID dispIdMember, final REFIID riid, LCID lcid, WORD wFlags,
final DISPPARAMS.ByReference pDispParams) {

final Method eventMethod;
if (CallbackProxy.this.dsipIdMap.containsKey(dispIdMember)) {
eventMethod = CallbackProxy.this.dsipIdMap.get(dispIdMember);
if (eventMethod.getParameterTypes().length != pDispParams.cArgs.intValue()) {
CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent(
"Trying to invoke method " + eventMethod + " with " + pDispParams.cArgs.intValue() + " arguments",
null);
return;
}
} else {
VARIANT[] arguments = pDispParams.getArgs();

final Method eventMethod = CallbackProxy.this.dsipIdMap.get(dispIdMember);
if (eventMethod == null) {
CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent(
"No method found with dispId = " + dispIdMember, null);
return;
}

/**
* DISPPARAMs provides two different ways to pass arguments.
*
* Arguments can be passed as a linear list with all arguments
* specified to a certain position (positional) or the position of
* an argument can be passed via the rgdispidNamedArgs array
* (named).
*
* pDispParams.rgvarg (length in pDispParams.cArgs) contains all
* arguments (named + position based)
*
* pDispParams.rgdispidNamedArgs (length in pDispParams.cNamedArgs)
* contains the named parameters as DISPIDs - the DISPIDs are the
* target index in the method signature (zero based).
*
* Each entry in pDispParams.rgvarg is either position based or name
* based and the position bases arguments are passed in reverse
* order, so getting this:
*
* rgvarg = ["arg1", "arg2", "arg3", "arg4", "arg5"]
* rgdispidNamedArgs = [3, 4]
*
* Would lead to this paramater array in the handler:
*
* ["arg5", "arg4", "arg3", "arg1", "arg2"]
*
* See also:
* https://msdn.microsoft.com/de-de/library/windows/desktop/ms221653%28v=vs.85%29.aspx
*/

// Arguments are converted to the JAVA side and IDispatch Interfaces
// are wrapped into an ProxyObject if so requested.
//
// Out-Parameter need to be specified as VARIANT, VARIANT args are
// not converted, so COM memory allocation rules apply.
final Class<?>[] params = eventMethod.getParameterTypes();
List<Object> rjargs = new ArrayList<Object>();
if (pDispParams.cArgs.intValue() > 0) {
VariantArg vargs = pDispParams.rgvarg;
vargs.setArraySize(pDispParams.cArgs.intValue());
for ( int i = 0; i < vargs.variantArg.length; i++) {
Class targetClass = params[vargs.variantArg.length - 1 - i];
Variant.VARIANT varg = vargs.variantArg[i];
Object jarg = Convert.toJavaObject(varg, targetClass, factory, true, false);
rjargs.add(jarg);

DISPID[] positionMap = pDispParams.getRgdispidNamedArgs();

final Class<?>[] paramTypes = eventMethod.getParameterTypes();
final Object[] params = new Object[paramTypes.length];

// Handle position based parameters first
for ( int i = 0; i < params.length && (arguments.length - positionMap.length - i) > 0; i++) {
Class targetClass = paramTypes[i];
Variant.VARIANT varg = arguments[arguments.length - i - 1];
params[i] = Convert.toJavaObject(varg, targetClass, factory, true, false);
}

for ( int i = 0; i < positionMap.length; i++) {
int targetPosition = positionMap[i].intValue();
if(targetPosition >= params.length) {
// If less parameters are mapped then supplied, ignore
continue;
}
Class targetClass = paramTypes[targetPosition];
Variant.VARIANT varg = arguments[i];
params[targetPosition] = Convert.toJavaObject(varg, targetClass, factory, true, false);
}

List<Object> margs = new ArrayList<Object>();
try {
// Reverse order from calling convention
int lastParamIdx = eventMethod.getParameterTypes().length - 1;
for (int i = lastParamIdx; i >= 0; i--) {
margs.add(rjargs.get(i));

// Make sure the parameters are correctly initialized -- primitives
// are initialized to their default value, else a NullPointer
// exception occurs while doing the call into the target method
for(int i = 0; i < params.length; i++) {
if(params[i] == null && paramTypes[i].isPrimitive()) {
if (paramTypes[i].equals(boolean.class)) {
params[i] = DEFAULT_BOOLEAN;
} else if (paramTypes[i].equals(byte.class)) {
params[i] = DEFAULT_BYTE;
} else if (paramTypes[i].equals(short.class)) {
params[i] = DEFAULT_SHORT;
} else if (paramTypes[i].equals(int.class)) {
params[i] = DEFAULT_INT;
} else if (paramTypes[i].equals(long.class)) {
params[i] = DEFAULT_LONG;
} else if (paramTypes[i].equals(float.class)) {
params[i] = DEFAULT_FLOAT;
} else if (paramTypes[i].equals(double.class)) {
params[i] = DEFAULT_DOUBLE;
} else {
throw new IllegalArgumentException("Class type " + paramTypes[i].getName() + " not mapped to primitive default value.");
}
}
eventMethod.invoke(comEventCallbackListener, margs.toArray());
}

try {
eventMethod.invoke(comEventCallbackListener, params);
} catch (Exception e) {
List<String> decodedClassNames = new ArrayList<String>(margs.size());
for(Object o: margs) {
List<String> decodedClassNames = new ArrayList<String>(params.length);
for(Object o: params) {
if(o == null) {
decodedClassNames.add("NULL");
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,8 +595,7 @@ protected HRESULT oleMethod(final int nType, final VARIANT.ByReference pvResult,

// Handle special-case for property-puts!
if (nType == OleAuto.DISPATCH_PROPERTYPUT) {
dp.cNamedArgs = new UINT(_argsLen);
dp.rgdispidNamedArgs = new DISPIDByReference(OaIdl.DISPID_PROPERTYPUT);
dp.setRgdispidNamedArgs(new DISPID[] {OaIdl.DISPID_PROPERTYPUT});
}

// Apply "fix" according to
Expand Down Expand Up @@ -631,9 +630,7 @@ protected HRESULT oleMethod(final int nType, final VARIANT.ByReference pvResult,

// Build DISPPARAMS
if (_argsLen > 0) {
dp.cArgs = new UINT(_args.length);
// make pointer of variant array
dp.rgvarg = new VariantArg.ByReference(_args);
dp.setArgs(_args);

// write 'DISPPARAMS' structure to memory
dp.write();
Expand Down
67 changes: 57 additions & 10 deletions contrib/platform/src/com/sun/jna/platform/win32/OleAuto.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
*/
package com.sun.jna.platform.win32;

import com.sun.jna.Memory;
import java.util.List;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.Guid.GUID;
import com.sun.jna.platform.win32.OaIdl.DISPIDByReference;
import com.sun.jna.platform.win32.OaIdl.DISPID;
import com.sun.jna.platform.win32.OaIdl.SAFEARRAY;
import com.sun.jna.platform.win32.OaIdl.SAFEARRAYBOUND;
import com.sun.jna.platform.win32.Variant.VARIANT;
Expand Down Expand Up @@ -563,15 +564,61 @@ public static class ByReference extends DISPPARAMS implements
/** The rgvarg. */
public VariantArg.ByReference rgvarg;

/** The rgdispid named args. */
public DISPIDByReference rgdispidNamedArgs;

/** The c args. */
public UINT cArgs;

/** The c named args. */
public UINT cNamedArgs;

/** The rgdispid named args. */
public Pointer rgdispidNamedArgs = Pointer.NULL;

/** The c args. - use setArgs to update arguments */
public UINT cArgs = new UINT(0);

/** The c named args. - use setRgdispidNamedArgs to update named arguments map */
public UINT cNamedArgs = new UINT(0);

public DISPID[] getRgdispidNamedArgs() {
DISPID[] namedArgs = null;
int count = cNamedArgs.intValue();
if(rgdispidNamedArgs != null && count > 0) {
int[] rawData = rgdispidNamedArgs.getIntArray(0, count);
namedArgs = new DISPID[count];
for(int i = 0; i < count; i++) {
namedArgs[i] = new DISPID(rawData[i]);
}
} else {
namedArgs = new DISPID[0];
}
return namedArgs;
}

public void setRgdispidNamedArgs(DISPID[] namedArgs) {
if(namedArgs == null) {
namedArgs = new DISPID[0];
}
cNamedArgs = new UINT(namedArgs.length);
rgdispidNamedArgs = new Memory(DISPID.SIZE * namedArgs.length);
int[] rawData = new int[namedArgs.length];
for(int i = 0; i < rawData.length; i++) {
rawData[i] = namedArgs[i].intValue();
}
rgdispidNamedArgs.write(0, rawData, 0, namedArgs.length);
}

public VARIANT[] getArgs() {
if(this.rgvarg != null) {
this.rgvarg.setArraySize(cArgs.intValue());
return this.rgvarg.variantArg;
} else {
return new VARIANT[0];
}
}

public void setArgs(VARIANT[] arguments) {
if(arguments == null) {
arguments = new VARIANT[0];
}

rgvarg = new VariantArg.ByReference(arguments);
cArgs = new UINT(arguments.length);
}

/**
* Instantiates a new dispparams.
*/
Expand Down
Loading

0 comments on commit 071e2af

Please sign in to comment.