1 | /* |
2 | * $Id: COM.java,v 1.38 2006/03/11 23:29:15 hastings Exp $ |
3 | * |
4 | * (c) Copyright, Moebius Solutions, Inc., 2004 |
5 | * |
6 | * All Rights Reserved |
7 | * |
8 | * This material may be reproduced by or for the U. S. Government |
9 | * pursuant to the copyright license under the clause at |
10 | * DFARS 252.227-7014 (OCT 2001). |
11 | */ |
12 | |
13 | package com.moesol.bindings.platform_sdk.component_services; |
14 | |
15 | import java.io.PrintWriter; |
16 | import java.io.StringWriter; |
17 | import java.lang.reflect.Field; |
18 | import java.net.MalformedURLException; |
19 | import java.nio.ByteBuffer; |
20 | import java.util.logging.Level; |
21 | import java.util.logging.Logger; |
22 | import java.util.prefs.BackingStoreException; |
23 | |
24 | import com.moesol.bindings.NativeLibraryLoader; |
25 | import com.moesol.bindings.platform_sdk.windows_api.HKEY; |
26 | import com.moesol.bindings.platform_sdk.windows_api.HWND; |
27 | import com.moesol.bindings.platform_sdk.windows_api.PlatformSDK; |
28 | import com.moesol.bindings.platform_sdk.windows_api.Win32Exception; |
29 | |
30 | /** |
31 | * @author robert |
32 | */ |
33 | public class COM { |
34 | private static final Logger logger = Logger.getLogger(COM.class.getName()); |
35 | |
36 | static { |
37 | NativeLibraryLoader.loadLibrary("com_moesol_bindings"); |
38 | } |
39 | |
40 | /** See CoCreateInstance */ |
41 | public static final int CLSCTX_INPROC_SERVER = 0x1; |
42 | /** See CoCreateInstance */ |
43 | public static final int CLSCTX_INPROC_HANDLER = 0x2; |
44 | /** See CoCreateInstance */ |
45 | public static final int CLSCTX_LOCAL_SERVER = 0x4; |
46 | /** See CoCreateInstance */ |
47 | public static final int CLSCTX_INPROC_SERVER16 = 0x8; |
48 | /** See CoCreateInstance */ |
49 | public static final int CLSCTX_REMOTE_SERVER = 0x10; |
50 | /** See CoCreateInstance */ |
51 | public static final int CLSCTX_INPROC_HANDLER16 = 0x20; |
52 | /** See CoCreateInstance, apparently CLSCTX_INPROC_SERVERX86 was deprecated */ |
53 | public static final int CLSCTX_RESERVED1 = 0x40; |
54 | // public static final int CLSCTX_INPROC_SERVERX86 = 0x40; |
55 | /** See CoCreateInstance, apparently CLSCTX_INPROC_HANDLERX86 was deprecated */ |
56 | public static final int CLSCTX_RESERVED2 = 0x80; |
57 | // public static final int CLSCTX_INPROC_HANDLERX86 = 0x80; |
58 | /** See CoCreateInstance, apparently CLSCTX_ESERVER_HANDLER was deprecated */ |
59 | public static final int CLSCTX_RESERVED3 = 0x100; |
60 | // public static final int CLSCTX_ESERVER_HANDLER = 0x100; |
61 | /** See CoCreateInstance */ |
62 | public static final int CLSCTX_RESERVED4 = 0x200; |
63 | /** See CoCreateInstance */ |
64 | public static final int CLSCTX_NO_CODE_DOWNLOAD = 0x400; |
65 | /** See CoCreateInstance */ |
66 | public static final int CLSCTX_RESERVED5 = 0x800; |
67 | /** See CoCreateInstance */ |
68 | public static final int CLSCTX_NO_CUSTOM_MARSHAL = 0x1000; |
69 | /** See CoCreateInstance */ |
70 | public static final int CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000; |
71 | /** See CoCreateInstance */ |
72 | public static final int CLSCTX_NO_FAILURE_LOG = 0x4000; |
73 | /** See CoCreateInstance */ |
74 | public static final int CLSCTX_DISABLE_AAA = 0x8000; |
75 | /** See CoCreateInstance */ |
76 | public static final int CLSCTX_ENABLE_AAA = 0x10000; |
77 | /** See CoCreateInstance */ |
78 | public static final int CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000; |
79 | |
80 | |
81 | /** See CoCreateInstance */ |
82 | public static final int CLSCTX_INPROC = (CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER); |
83 | /** See CoCreateInstance */ |
84 | public static final int CLSCTX_ALL = (CLSCTX_INPROC_SERVER |
85 | | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER); |
86 | /** See CoCreateInstance */ |
87 | public static final int CLSCTX_SERVER = (CLSCTX_INPROC_SERVER |
88 | | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER); |
89 | /** See CoCreateInstance */ |
90 | public static final int CLSCTX_LOCAL = (CLSCTX_INPROC_SERVER |
91 | | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER); |
92 | |
93 | |
94 | /** See InprocServer32 in MSDN */ |
95 | public static final String ThreadingModel_Apartment = "Apartment"; |
96 | /** See InprocServer32 in MSDN */ |
97 | public static final String ThreadingModel_Both = "Both"; |
98 | /** See InprocServer32 in MSDN */ |
99 | public static final String ThreadingModel_Free = "Free"; |
100 | /** See InprocServer32 in MSDN */ |
101 | public static final String ThreadingModel_Neutral = "Neutral"; |
102 | |
103 | /** |
104 | * Adds a COM event listener to source. Internally, addListener |
105 | * uses the IConnectPointContainer of the source to find |
106 | * the IConnectionPoint and the add listener. |
107 | * |
108 | * @param source |
109 | * @param listener |
110 | * @param diid |
111 | */ |
112 | public static int addListener(IUnknown source, IUnknown listener, GUID diid) { |
113 | IUnknown.Jni unk_jni = (IUnknown.Jni)source; |
114 | return IUnknown.Jni.MoeSolInternal.addListener(unk_jni, listener, diid); |
115 | } |
116 | |
117 | /** |
118 | * Removes a COM event listener from source. |
119 | * |
120 | * @param source |
121 | * @param listener |
122 | * @param diid |
123 | */ |
124 | public static void removeListener(IUnknown source, IUnknown listener, GUID diid) { |
125 | IUnknown.Jni unk_jni = (IUnknown.Jni)source; |
126 | IUnknown.Jni.MoeSolInternal.removeListener(unk_jni, listener, diid); |
127 | } |
128 | |
129 | /** |
130 | * The method should probably have default access instead of public. It is kept public |
131 | * for backward compatibility with older releases. This method will be marked deprecated |
132 | * with tlb2java automatically wraps java interfaces with native proxies. |
133 | * |
134 | * @param java_sink |
135 | * @param out_iface |
136 | */ |
137 | public static void wrapJavaWithNativeDispatch(IDispatch java_sink, Object[] out_iface) { |
138 | InterfaceBuilder ib = new InterfaceBuilder(out_iface); |
139 | jni_wrap(java_sink, ib.getIIDBytes(), ib.getResult()); |
140 | out_iface[0] = ib.getResult(); |
141 | } |
142 | |
143 | /** |
144 | * Note that tlb2java should auto-wrap when a java interface is passed in and it |
145 | * is not vtable based! |
146 | */ |
147 | public static void wrapJavaWithVtableInterface(ComObject java_sink, Object[] out_iface) { |
148 | InterfaceBuilder ib = new InterfaceBuilder(out_iface); |
149 | out_iface[0] = java_sink.m_iunknown_support.queryInterface(ib.getIID()); |
150 | } |
151 | |
152 | /** |
153 | * See CoCreateInstance |
154 | * |
155 | * @param rclsid |
156 | * @param pUnkOuter |
157 | * @param dwClsContext |
158 | * @param out_iface |
159 | * |
160 | * @return HRESULT |
161 | */ |
162 | public static int CoCreateInstance(GUID rclsid, IUnknown pUnkOuter, |
163 | int dwClsContext, Object[] out_iface) |
164 | { |
165 | out_iface[0] = null; |
166 | if (pUnkOuter != null && !(pUnkOuter instanceof IUnknown.Jni)) { |
167 | throw new COMException("Java objects cannot be outer unknown for aggregation", HRESULT.E_INVALIDARG); |
168 | } |
169 | IUnknown.Jni outer = (IUnknown.Jni)pUnkOuter; |
170 | |
171 | InterfaceBuilder ib = new InterfaceBuilder(out_iface); |
172 | int hr = jni_CoCreateInstance(rclsid._getStructureBytes(), outer, |
173 | dwClsContext, ib.getIIDBytes(), ib.getResult()); |
174 | if (HRESULT.FAILED(hr)) { |
175 | throw new COMException(hr); |
176 | } |
177 | out_iface[0] = ib.getResult(); |
178 | return hr; |
179 | } |
180 | |
181 | public static GUID CoCreateGuid() { |
182 | GUID guid = new GUID(); |
183 | int hr = jni_CoCreateGuid(guid._getStructureBytes()); |
184 | if (HRESULT.FAILED(hr)) { |
185 | throw new COMException(hr); |
186 | } |
187 | return guid; |
188 | } |
189 | |
190 | public static int OleCreatePropertyFrame( |
191 | HWND hwndOwner, //Parent window of property sheet dialog box |
192 | int x, //Horizontal position for dialog box |
193 | int y, //Vertical position for dialog box |
194 | String lpszCaption, |
195 | //Pointer to the dialog box caption |
196 | IUnknown[] objects, |
197 | //Pointer to the objects for property sheet |
198 | GUID[] lpPageClsID, |
199 | //Array of CLSIDs for each property page |
200 | int lcid, //Locale identifier for property sheet locale |
201 | int dwReserved, //Reserved |
202 | Object lpvReserved //Reserved |
203 | ) |
204 | { |
205 | GUID.Array array = convertGuidArray(lpPageClsID); |
206 | |
207 | int hr = jni_OleCreatePropertyFrame(HWND._safeGetHandle(hwndOwner), x, y, lpszCaption, |
208 | objects.length, objects, |
209 | lpPageClsID.length, array._getByteBuffer(), |
210 | lcid, dwReserved, lpvReserved); |
211 | if (HRESULT.FAILED(hr)) { |
212 | throw new COMException(hr); |
213 | } |
214 | return hr; |
215 | } |
216 | |
217 | private static GUID.Array convertGuidArray(GUID[] lpPageClsID) { |
218 | ByteBuffer guid_bb = ByteBuffer.allocate(GUID.sizeof() * lpPageClsID.length); |
219 | GUID.Array array = new GUID.Array(guid_bb); |
220 | for (int i = 0; i < lpPageClsID.length; i++) { |
221 | array.put(i, lpPageClsID[i]); |
222 | } |
223 | return array; |
224 | } |
225 | |
226 | |
227 | /** |
228 | * Turn off apartment thread checking for unk. |
229 | * @param unk |
230 | */ |
231 | public static void disableApartmentChecking(IUnknown unk) { |
232 | IUnknown.Jni unk_jni = (IUnknown.Jni)unk; |
233 | IUnknown.Jni.MoeSolInternal.disableApartmentChecking(unk_jni); |
234 | } |
235 | |
236 | /** |
237 | * Turn on apartment thread checking for unk. |
238 | * @param unk |
239 | */ |
240 | public static void recordApartment(IUnknown unk) { |
241 | IUnknown.Jni unk_jni = (IUnknown.Jni)unk; |
242 | IUnknown.Jni.MoeSolInternal.recordApartment(unk_jni); |
243 | } |
244 | |
245 | /** |
246 | * See OleInitialize. You do not need to call this method if you use an |
247 | * {@code OleThread}. |
248 | * @return HRESULT |
249 | * @see OleThread |
250 | */ |
251 | public static int OleInitialize() { |
252 | int hr = jni_OleInitialize(); |
253 | if (HRESULT.FAILED(hr)) { |
254 | throw new COMException(hr); |
255 | } |
256 | incInitCount(); |
257 | return hr; |
258 | } |
259 | /** |
260 | * See OleUninitialize. You do not need to call this method if you use an |
261 | * {@code OleThread}. |
262 | * @see OleThread |
263 | */ |
264 | public static void OleUninitialize() { |
265 | synchronized (COM.class) { |
266 | decInitCount(); |
267 | if (g_init_count == 0) { |
268 | checkSafety(); |
269 | } |
270 | } |
271 | jni_OleUninitialize(); |
272 | } |
273 | private static void checkSafety() { |
274 | if (COM.getUnreleasedCount() == 0) { |
275 | // Fast exit |
276 | return; |
277 | } |
278 | |
279 | COM.gc(); |
280 | |
281 | if (COM.getUnreleasedCount() != 0) { |
282 | logger.log(Level.SEVERE, |
283 | "Unreleased ({0}) interfaces may crash JVM.", new Integer( |
284 | COM.getUnreleasedCount())); |
285 | } |
286 | assert(COM.getUnreleasedCount() == 0); |
287 | } |
288 | |
289 | /** |
290 | * Call System.gc in a loop for upto one second to |
291 | * release all COM interfaces. |
292 | * Since System.gc may not always call finalize on all finalizable |
293 | * objects we loop for upto one second calling System.gc and Thread.yield. |
294 | * If the unreleased count goes to zero return immediately. |
295 | */ |
296 | public static void gc() { |
297 | long start_millis = System.currentTimeMillis(); |
298 | long end_millis = start_millis + 1000; |
299 | while (COM.getUnreleasedCount() > 0 && (System.currentTimeMillis() < end_millis)) { |
300 | System.gc(); |
301 | Thread.yield(); |
302 | } |
303 | } |
304 | |
305 | static private long g_init_count; |
306 | static private synchronized void incInitCount() { |
307 | g_init_count++; |
308 | } |
309 | static private synchronized void decInitCount() { |
310 | g_init_count--; |
311 | } |
312 | static private native int jni_OleInitialize(); |
313 | static private native void jni_OleUninitialize(); |
314 | static private native int jni_CoCreateGuid(byte[] guidBytes); |
315 | static private native int jni_OleCreatePropertyFrame( |
316 | long hwndOwner, int x, int y, |
317 | String lpszCaption, int count_objects, IUnknown[] objects, int count_guids, ByteBuffer guid_bb, |
318 | int lcid, int dwReserved, Object lpvReserved); |
319 | |
320 | static private native int jni_CoCreateInstance(byte[] rclsid, IUnknown.Jni pUnkOuter, |
321 | int dwClsContext, byte[] riid, IUnknown out_ppv); |
322 | static private native void jni_wrap(IDispatch java_sink, byte[] diid, IUnknown out_ppv); |
323 | |
324 | |
325 | /** |
326 | * Get the IID of the most derived interface that <code>unk</code> |
327 | * represents. Used in the unit tests to confirm QI is working correctly. |
328 | * |
329 | * @param unk |
330 | * |
331 | * @return a GUID that is the IID of the most derived interface that |
332 | * <code>unk</code> represents. |
333 | */ |
334 | public static GUID extractIID(IUnknown unk) { |
335 | return extractIID(findIIDField(unk.getClass())); |
336 | } |
337 | private static GUID extractIID(Field iid_field) { |
338 | try { |
339 | return (GUID) iid_field.get(null); |
340 | } catch (Exception e) { |
341 | throw new RuntimeException(e); |
342 | } |
343 | } |
344 | private static Field findIIDField(Class iface_type) { |
345 | try { |
346 | return iface_type.getField("IID"); |
347 | } catch (Exception e) { |
348 | throw new RuntimeException(e); |
349 | } |
350 | } |
351 | |
352 | /** |
353 | * The unreleased count is the count of all IUnknown and subclasses that |
354 | * have native COM interfaces attached. To free these native COM interfaces |
355 | * the IUnknown.Release must be called. |
356 | * |
357 | * @return Count of all COM interfaces that have not been released. |
358 | */ |
359 | public static int getUnreleasedCount() { |
360 | return IUnknown.Jni.MoeSolInternal.getUnreleasedCount(); |
361 | } |
362 | |
363 | /** |
364 | * @return the number of generic sinks that have not been released. |
365 | */ |
366 | public static int getGenericSinkInstanceCount() { |
367 | return _getGenericSinkInstanceCount(); |
368 | } |
369 | private static native int _getGenericSinkInstanceCount(); |
370 | |
371 | /** |
372 | * @return the number of thunks that have not been relesed. |
373 | */ |
374 | public static int getVtableThunkCount() { |
375 | return jni_getVtableThunkCount(); |
376 | } |
377 | private static native int jni_getVtableThunkCount(); |
378 | |
379 | static IUnknown dllGetClassObject(ByteBuffer bb_clsid, ByteBuffer bb_iid) { |
380 | GUID clsid = new GUID(bb_clsid); |
381 | GUID iid = new GUID(bb_iid); |
382 | try { |
383 | String class_name = findComServerValue(clsid, "Class"); |
384 | Class server_class = ComInProcGroups.instance().getClass(clsid, class_name); |
385 | ComClassFactory factory = new ComClassFactory(server_class); |
386 | return factory.m_iunknown_support.queryInterface(iid); |
387 | } catch (ClassCastException e) { |
388 | logger.log(Level.INFO, "caught", e); |
389 | throw new COMException(COM.mapExceptionToHRESULT(e)); |
390 | } catch (ClassNotFoundException e) { |
391 | logger.log(Level.INFO, "caught", e); |
392 | throw new COMException(COM.mapExceptionToHRESULT(e)); |
393 | } catch (MalformedURLException e) { |
394 | logger.log(Level.INFO, "caught", e); |
395 | throw new COMException(COM.mapExceptionToHRESULT(e)); |
396 | } catch (BackingStoreException e) { |
397 | logger.log(Level.INFO, "caught", e); |
398 | throw new COMException(COM.mapExceptionToHRESULT(e)); |
399 | } |
400 | } |
401 | |
402 | static String findComServerValue(GUID clsid, String value_name) { |
403 | try { |
404 | HKEY out_key[] = { null }; |
405 | PlatformSDK.RegOpenKeyEx(PlatformSDK.HKEY_CLASSES_ROOT, "CLSID\\" + clsid.toString() + "\\InProcServer32", 0, PlatformSDK.KEY_READ, out_key); |
406 | try { |
407 | String[] out_value = { null }; |
408 | PlatformSDK.RegQueryValue(out_key[0], value_name, out_value); |
409 | return out_value[0]; |
410 | } finally { |
411 | PlatformSDK.RegCloseKey(out_key[0]); |
412 | } |
413 | } catch (Win32Exception we) { |
414 | throw new COMException(mapExceptionToHRESULT(we)); |
415 | } |
416 | } |
417 | |
418 | /** |
419 | * @return The full path to the com_moesol_bindings.dll. |
420 | */ |
421 | static String getBindingsPath() { |
422 | String r = jni_getBindingsPath("com_moesol_bindings.dll"); |
423 | if (r == null) { |
424 | throw new Win32Exception(PlatformSDK.GetLastError()); |
425 | } |
426 | return r; |
427 | } |
428 | |
429 | private static native String jni_getBindingsPath(String module); |
430 | |
431 | /** |
432 | * Called by JNI code to get the best HRESULT for {@code t}. |
433 | * |
434 | * @param t |
435 | * @return HRESULT |
436 | */ |
437 | static int uncaughtJavaException(Throwable t) { |
438 | logger.log(Level.FINE, "Java COM component threw exception", t); |
439 | return mapExceptionToHRESULT(t); |
440 | } |
441 | |
442 | /** |
443 | * Called by JNI code to get the stack track for {@code t} |
444 | * |
445 | * @param t |
446 | * @return String representation of the stack trace for {@code t} |
447 | */ |
448 | static String computeStringForJavaException(Throwable t) { |
449 | StringWriter sw = new StringWriter(); |
450 | PrintWriter pw = new PrintWriter(sw); |
451 | t.printStackTrace(pw); |
452 | pw.close(); |
453 | return sw.toString(); |
454 | } |
455 | |
456 | /** |
457 | * @param t |
458 | * @return best HRESULT based on {@code t}. |
459 | */ |
460 | public static int mapExceptionToHRESULT(Throwable t) { |
461 | if (t instanceof OutOfMemoryError) { |
462 | return HRESULT.E_OUTOFMEMORY; |
463 | } else if (t instanceof UnsupportedOperationException) { |
464 | return HRESULT.E_NOTIMPL; |
465 | } else if (t instanceof IllegalAccessException) { |
466 | return HRESULT.E_ACCESSDENIED; |
467 | } else if (t instanceof IllegalArgumentException) { |
468 | return HRESULT.E_INVALIDARG; |
469 | } else if (t instanceof NullPointerException) { |
470 | return HRESULT.E_POINTER; |
471 | } else if (t instanceof IllegalStateException) { |
472 | return HRESULT.E_UNEXPECTED; |
473 | } else if (t instanceof ClassCastException) { |
474 | return HRESULT.DISP_E_TYPEMISMATCH; |
475 | } else if (t instanceof InstantiationException) { |
476 | return HRESULT.CO_E_CLASS_CREATE_FAILED; |
477 | } else if (t instanceof IllegalAccessException) { |
478 | return HRESULT.E_ACCESSDENIED; |
479 | } else if (t instanceof ClassNotFoundException) { |
480 | return HRESULT.CLASS_E_CLASSNOTAVAILABLE; |
481 | } else if (t instanceof Win32Exception) { |
482 | Win32Exception we = (Win32Exception)t; |
483 | return HRESULT.HRESULT_FROM_WIN32(we.getLRESULT()); |
484 | } else if (t instanceof MalformedURLException) { |
485 | return HRESULT.MK_E_SYNTAX; |
486 | } else if (t instanceof BackingStoreException) { |
487 | return HRESULT.REGDB_E_READREGDB; |
488 | } else if (t instanceof COMException) { |
489 | COMException ce = (COMException)t; |
490 | return ce.getHRESULT(); |
491 | } |
492 | |
493 | return HRESULT.E_FAIL; |
494 | } |
495 | |
496 | public static void Invoke(IDispatch callee, int dispIdMember, GUID riid, int lcid, short wFlags, |
497 | Object[] dispParams, Object[] varResult) |
498 | { |
499 | IDispatch.Jni inner_jni = (IDispatch.Jni) callee; |
500 | inner_jni.Invoke(dispIdMember, riid, lcid, wFlags, dispParams, varResult); |
501 | } |
502 | } |