Using COM from Java via JNA
According to the COM spec a COM interface pointer is just a pointer to a pointer that points to an array of function pointers. So I was wondering: Can I call these function pointers from Java somehow? Not really to get any usefulness from it, but rather to learn a bit of COM and have a bit of fun.
Now JNA is a great library that I’ve used a couple of times. It allows you to call code in native DLL’s by only writing Java code, no JNI. JNA also comes with alot of advanced tools to manipulate native memory and pointers from right within Java. Theoretically it should be able to do COM invocations. I went on and tried it out, and this is what I came up with. It actually works:
The IUnknown interface
@IId("00000000-0000-0000-C000-000000000046")
public interface IUnknown {
int QueryInterface(
Guid riid,
PointerByReference ppvObject);
int AddRef();
int Release();
}
And a bit of code to create a COM object given a CLSID and return an IUnkown interface for it :
public static IUnknown createInstance(Guid clsId) {
IId iidA = IUnknown.class.getAnnotation(IId.class);
if (iidA == null)
throw new IllegalArgumentException("Interface needs to be annotated with an IId");
Guid iId = new Guid(iidA.value());
Ole32 ole32 = WindowsJna.OLE32.get();
PointerByReference p = new PointerByReference();
int res = ole32.CoCreateInstance(clsId, null, Ole32.CLSCTX_INPROC, iId, p);
if (res != Ole32.S_OK)
throw new WinApiException(res);
final Pointer interfacePointer = p.getValue();
final Pointer vTablePointer = interfacePointer.getPointer(0);
final Pointer[] vTable = new Pointer[3];
vTablePointer.read(0, vTable, 0, 3);
return new IUnknown() {
public int QueryInterface(Guid riid, PointerByReference ppvObject) {
Function f = Function.getFunction(vTable[0], Function.ALT_CONVENTION);
return f.invokeInt(new Object[] { interfacePointer, riid, ppvObject });
}
public int AddRef() {
Function f = Function.getFunction(vTable[1], Function.ALT_CONVENTION);
return f.invokeInt(new Object[] { interfacePointer });
}
public int Release() {
Function f = Function.getFunction(vTable[2], Function.ALT_CONVENTION);
return f.invokeInt(new Object[] { interfacePointer });
}
};
}
The next step would be to implement IDispatch. If I have that then I can perhaps throw in some Dynamic Proxy magic again to map Java interfaces to IDispatch interfaces using JNA in the background.
Edit: I’m adding more code here as requested by Jarvichi
WinApiException looks like this:
/**
* Exception generated because a Windows API function failed with an error code.
*
* @author Hannes de Jager
* @since 11 Apr 2011
*/
public class WinApiException extends RuntimeException {
private int errorCode;
public WinApiException() {
this(WindowsJna.KERNEL32.get().GetLastError());
}
public WinApiException(int code) {
super("Error "+code+": "+formatMessageFromHR(code));
this.errorCode = code;
}
public int getErrorCode() {
return errorCode;
}
public static String formatMessageFromHR(int code) {
PointerByReference buffer = new PointerByReference();
if (0 == WindowsJna.KERNEL32.get().FormatMessage(0x00000100 | 0x00001000 | 0x00000200, null, code, 0, buffer, 0, null)) {
return "";
}
String s = buffer.getValue().getString(0, ! Boolean.getBoolean("w32.ascii"));
s = s.replace(".\r",".").replace(".\n",".");
WindowsJna.KERNEL32.get().LocalFree(buffer.getValue());
return s;
}
public static IOException asNewIoException() {
return new IOException(new WinApiException().getMessage());
}
}
IID is my own custom annotation. It looks like this:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IId {
public String value();
}
Also this class here contains the code for OLE32. (Where you will find Ole32.CLSCTX_INPROC):
/**
* JNA Interface to the Windows API functions in Ole32.dll which implements the COM library functions.
*
* @author Hannes de Jager - With some help from http://code.google.com/p/bridj/
* @since 2011-11-10
*/
public interface Ole32 extends StdCallLibrary {
public static final int
CLSCTX_INPROC_SERVER = 0x1,
CLSCTX_INPROC_HANDLER = 0x2,
CLSCTX_LOCAL_SERVER = 0x4,
CLSCTX_INPROC_SERVER16 = 0x8,
CLSCTX_REMOTE_SERVER = 0x10,
CLSCTX_INPROC_HANDLER16 = 0x20,
CLSCTX_RESERVED1 = 0x40,
CLSCTX_RESERVED2 = 0x80,
CLSCTX_RESERVED3 = 0x100,
CLSCTX_RESERVED4 = 0x200,
CLSCTX_NO_CODE_DOWNLOAD = 0x400,
CLSCTX_RESERVED5 = 0x800,
CLSCTX_NO_CUSTOM_MARSHAL = 0x1000,
CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000,
CLSCTX_NO_FAILURE_LOG = 0x4000,
CLSCTX_DISABLE_AAA = 0x8000,
CLSCTX_ENABLE_AAA = 0x10000,
CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000,
CLSCTX_ACTIVATE_32_BIT_SERVER = 0x40000,
CLSCTX_ACTIVATE_64_BIT_SERVER = 0x80000,
CLSCTX_ENABLE_CLOAKING = 0x100000,
CLSCTX_PS_DLL = 0x80000000;
public static final int
CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER,
CLSCTX_ALL = (CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER),
CLSCTX_SERVER = (CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER);
public static final int
S_OK = 0,
S_FALSE = 1,
REGDB_E_CLASSNOTREG = 0x80040154,
CLASS_E_NOAGGREGATION = 0x80040110,
CO_E_NOTINITIALIZED = 0x800401F0;
public static final int E_UNEXPECTED = 0x8000FFFF;
public static final int E_NOTIMPL = 0x80004001;
public static final int E_OUTOFMEMORY = 0x8007000E;
public static final int E_INVALIDARG = 0x80070057;
public static final int E_NOINTERFACE = 0x80004002;
public static final int E_POINTER = 0x80004003;
public static final int E_HANDLE = 0x80070006;
public static final int E_ABORT = 0x80004004;
public static final int E_FAIL = 0x80004005;
public static final int E_ACCESSDENIED = 0x80070005;
public static final int DISP_E_BADVARTYPE = -2147352568;
public static final int DISP_E_NOTACOLLECTION = -2147352559;
public static final int DISP_E_MEMBERNOTFOUND = -2147352573;
public static final int DISP_E_ARRAYISLOCKED = -2147352563;
public static final int DISP_E_EXCEPTION = -2147352567;
public static final int DISP_E_TYPEMISMATCH = -2147352571;
public static final int DISP_E_BADINDEX = -2147352565;
public static final int DISP_E_BADCALLEE = -2147352560;
public static final int DISP_E_OVERFLOW = -2147352566;
public static final int DISP_E_UNKNOWNINTERFACE = -2147352575;
public static final int DISP_E_DIVBYZERO = -2147352558;
public static final int DISP_E_UNKNOWNLCID = -2147352564;
public static final int DISP_E_PARAMNOTOPTIONAL = -2147352561;
public static final int DISP_E_PARAMNOTFOUND = -2147352572;
public static final int DISP_E_BADPARAMCOUNT = -2147352562;
public static final int DISP_E_BUFFERTOOSMALL = -2147352557;
public static final int DISP_E_UNKNOWNNAME = -2147352570;
public static final int DISP_E_NONAMEDARGS = -2147352569;
public static final int COINIT_APARTMENTTHREADED = 0x2; // Apartment model
public static final int COINIT_MULTITHREADED = 0x0; // OLE calls objects on any thread.
public static final int COINIT_DISABLE_OLE1DDE = 0x4; // Don't use DDE for Ole1 support.
public static final int COINIT_SPEED_OVER_MEMORY = 0x8;
public HResult CoCreateGuid(Pointer pointer);
public HResult CoInitialize(Pointer pvReserved);
public HResult CoInitializeEx(Pointer pvReserved, int dwCoInit);
public HResult CoUninitialize();
HResult CoCreateInstance(
Guid rclsid,
Pointer pUnkOuter,
int dwClsContext,
Guid riid,
PointerByReference ppv
);
HResult CoGetClassObject(
Guid rclsid,
int dwClsContext,
Pointer pServerInfo, //__in_opt COSERVERINFO *pServerInfo
Guid riid,
PointerByReference ppv
);
HResult CLSIDFromProgID(String lpszProgID, Guid lpclsid);
HResult ProgIDFromCLSID(Guid clsid, Pointer lplpszProgID);
HResult CoMarshalInterThreadInterfaceInStream(
Guid riid,
Pointer pUnk, // __in LPUNKNOWN pUnk
PointerByReference ppStm //__out LPSTREAM *ppStm
);
HResult CoGetInterfaceAndReleaseStream(
Pointer pStm, //__in LPSTREAM
Guid iid,
PointerByReference ppv //__out LPVOID *ppv
);
void CoTaskMemFree(Pointer pv);
Pointer CoTaskMemAlloc(int cb);
}