1 | /* |
2 | * $Id: IUnknown.java,v 1.24 2006/03/04 06:04:14 hastings Exp $ |
3 | * |
4 | * Copyright (c) 2004, Moebius Solutions, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * Redistribution and use in source and binary forms, with or without |
8 | * modification, are permitted provided that the following conditions |
9 | * are met: |
10 | * |
11 | * Redistributions of source code must retain the above copyright |
12 | * notice, this list of conditions and the following disclaimer. |
13 | * |
14 | * Redistributions in binary form must reproduce the above |
15 | * copyright notice, this list of conditions and the following |
16 | * disclaimer in the documentation and/or other materials provided |
17 | * with the distribution. |
18 | * |
19 | * Neither the name of Moebius Solutions, Inc. nor the names of |
20 | * its contributors may be used to endorse or promote products |
21 | * derived from this software without specific prior written |
22 | * permission. |
23 | * |
24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
25 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
26 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
27 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
28 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
30 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
31 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
32 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
33 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
34 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
35 | * OF THE POSSIBILITY OF SUCH DAMAGE. |
36 | */ |
37 | |
38 | package com.moesol.bindings.platform_sdk.component_services; |
39 | |
40 | import java.util.Vector; |
41 | import java.util.logging.Level; |
42 | import java.util.logging.Logger; |
43 | |
44 | import com.moesol.bindings.AllocationLocation; |
45 | import com.moesol.bindings.DispatchJavaMethod; |
46 | import com.moesol.bindings.platform_sdk.windows_api.PlatformSDK; |
47 | |
48 | /** See IUnknown in MSDN */ |
49 | public interface IUnknown { |
50 | public static final Class TYPELIB = TYPELIB_00020430_0000_0000_C000_000000000046.class; |
51 | /** IID for Unknown {00000000-0000-0000-C000-000000000046} */ |
52 | public static final com.moesol.bindings.platform_sdk.component_services.GUID IID = new com.moesol.bindings.platform_sdk.component_services.GUID( |
53 | 0x00000000, (short) 0x0000, (short) 0x0000, new byte[] { |
54 | (byte) 0xC0, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
55 | (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x46 }); |
56 | |
57 | /** |
58 | * See IUnknown |
59 | * |
60 | * @param out_iface |
61 | * Result of the QueryInterface is placed in out_iface[0] |
62 | */ |
63 | public void QueryInterface(Object[] out_iface); |
64 | |
65 | /** |
66 | * See IUnknown |
67 | * |
68 | * @param iface_class |
69 | * Class to QueryInterface to, must have a static IID field |
70 | * @return Result of QueryInterface, cast to iface_class. |
71 | */ |
72 | public IUnknown QueryInterface(Class iface_class); |
73 | |
74 | /** |
75 | * See IUnknown |
76 | * |
77 | * @return May be new reference count. |
78 | */ |
79 | public int Release(); |
80 | |
81 | /** Java to COM implementation. */ |
82 | public static class Jni implements IUnknown { |
83 | |
84 | /** |
85 | * You cannot create an interface from thin air so protect default ctor. |
86 | * Instead, you can use COM.CoCreateInstance or new [coclass]. |
87 | * |
88 | * Creates an IUnknown pointer with the value NULL. |
89 | */ |
90 | protected Jni() { |
91 | // Empty |
92 | } |
93 | |
94 | /** |
95 | * Call IUnknown::QueryInterface on the native COM pointer. By |
96 | * convention out parameters are passed as an array of size one. This |
97 | * method extracts the IID from <code>out_iface</code>, so you should |
98 | * pass in an array of the type of interface you want. For example, |
99 | * |
100 | * <pre> |
101 | * |
102 | * |
103 | * IUnknown unknown = an_unknown_argument; |
104 | * IFooInterface[] out_foo_interface = { null }; |
105 | * unknown.QueryInterface(out_foo_interface); |
106 | * try { |
107 | * out_foo_interface[0].BarMethodOnFoo(); |
108 | * } finally { |
109 | * out_foo_interface[0].Release(); |
110 | * } |
111 | * |
112 | * |
113 | * </pre> |
114 | * |
115 | * @param out_iface |
116 | * Array of size one to receive the new interface. |
117 | */ |
118 | public void QueryInterface(Object[] out_iface) { |
119 | InterfaceBuilder ib = new InterfaceBuilder(out_iface); |
120 | QueryInterface(ib); |
121 | out_iface[0] = ib.getResult(); |
122 | } |
123 | |
124 | /** |
125 | * Call IUnknown::QueryInterface on the native COM pointer. The returned |
126 | * interface must be released with a call to Release. You must cast the |
127 | * return value to the same type as described by |
128 | * <code>iface_class</code>. |
129 | * |
130 | * <pre> |
131 | * IUnknown unknown = an_unknown_argument; |
132 | * IFooInterface foo_interface = null; |
133 | * foo_interface = (IFooInterface) unknown.QueryInterface(IFooInterface.class); |
134 | * try { |
135 | * foo_interface.BarMethodOnFoo(); |
136 | * } finally { |
137 | * foo_interface.Release(); |
138 | * } |
139 | * </pre> |
140 | * |
141 | * @param iface_class |
142 | * @return new interface |
143 | */ |
144 | public IUnknown QueryInterface(Class iface_class) { |
145 | InterfaceBuilder ib = new InterfaceBuilder(iface_class); |
146 | QueryInterface(ib); |
147 | return ib.getResult(); |
148 | } |
149 | |
150 | private void QueryInterface(InterfaceBuilder ib) { |
151 | int hr; |
152 | hr = jni_QueryInterface(m_iface_ptr, ib.getIIDBytes(), ib |
153 | .getResult()); |
154 | if (HRESULT.FAILED(hr)) { |
155 | throw new COMException(hr); |
156 | } |
157 | } |
158 | |
159 | /** |
160 | * Calls the native COM interfaces IUnknown::Release method and make |
161 | * this equivalent to NULL. Calling COM methods after this call will |
162 | * throw a COMException. If this object referered to a valid native COM |
163 | * interface before the call, this method causes the COM unreleased |
164 | * count to be reduced by one when the interface is released. |
165 | * |
166 | * @return an integer which may represent the remaining numbers of |
167 | * references on the COM object. See IUknown for more details. |
168 | */ |
169 | public int Release() { |
170 | if (isApartmentChecked()) { |
171 | checkApartment(); |
172 | } |
173 | try { |
174 | return jni_Release(m_iface_ptr); |
175 | } finally { |
176 | m_iface_ptr = 0; |
177 | } |
178 | } |
179 | |
180 | /** |
181 | * |
182 | * @param sink |
183 | * @param diid |
184 | * @return cookie |
185 | */ |
186 | private int addListener(IUnknown sink, GUID diid) { |
187 | int hr; |
188 | int[] out_cookie = { 0 }; |
189 | hr = jni_addListener(sink, diid._getStructureBytes(), out_cookie); |
190 | HRESULT.throwOnFailed(hr); |
191 | return out_cookie[0]; |
192 | } |
193 | |
194 | private void removeListener(GUID diid, int cookie) { |
195 | int hr; |
196 | hr = jni_removeListener(diid._getStructureBytes(), cookie); |
197 | HRESULT.throwOnFailed(hr); |
198 | } |
199 | |
200 | /** |
201 | * Uses the COM property that if you QueryInterface to two interface |
202 | * pointers to IUnknown the pointers returned will be equals if and only |
203 | * if the interfaces refer to the same object. |
204 | * |
205 | * @param obj |
206 | * A COM interface wrapper. |
207 | * |
208 | * @return true if this interface refers to the same COM server as |
209 | * <code>obj</code> |
210 | */ |
211 | public boolean equals(Object obj) { |
212 | if (!(obj instanceof IUnknown.Jni)) { |
213 | return false; |
214 | } |
215 | IUnknown.Jni any_iface = (IUnknown.Jni)obj; |
216 | |
217 | if (m_iface_ptr == any_iface.m_iface_ptr) { |
218 | // Short circuit true, handles case where we are both 0. |
219 | return true; |
220 | } |
221 | if (any_iface.m_iface_ptr == 0) { |
222 | // Probably should not happen. |
223 | return false; |
224 | } |
225 | |
226 | IUnknown.Jni[] left = { null }; |
227 | QueryInterface(left); |
228 | try { |
229 | IUnknown.Jni[] right = { null }; |
230 | any_iface.QueryInterface(right); |
231 | try { |
232 | return left[0].m_iface_ptr == right[0].m_iface_ptr; |
233 | } finally { |
234 | right[0].Release(); |
235 | } |
236 | } finally { |
237 | left[0].Release(); |
238 | } |
239 | } |
240 | |
241 | public int hashCode() { |
242 | // We must hash on IUnknown |
243 | IUnknown.Jni[] left = { null }; |
244 | QueryInterface(left); |
245 | try { |
246 | // We can drop the right two bits because pointers are |
247 | // 4 byte aligned, so 0x04, 0x05, 0x06, 0x07 all get mapped |
248 | // to 0x04. |
249 | return ((int)left[0].m_iface_ptr) >> 2; |
250 | } finally { |
251 | left[0].Release(); |
252 | } |
253 | } |
254 | |
255 | /** |
256 | * The returned string includes the class name and the hexadecimal |
257 | * representation of the underlying native COM pointer. |
258 | * |
259 | * @return a string describing this object |
260 | */ |
261 | public String toString() { |
262 | return getClass().getName() + "@0x" + Long.toHexString(m_iface_ptr); |
263 | } |
264 | |
265 | /** |
266 | * If Release has not been called, <code>finalize</code> calls it. For |
267 | * apartment model interfaces finalize uses invokeAndWait to call |
268 | * release on the correct apartment thread. |
269 | */ |
270 | protected void finalize() { |
271 | try { |
272 | if (isNullPtr()) { |
273 | return; |
274 | } |
275 | if (isReleaseThreadSet()) { |
276 | releaseOnReleaseThread(); |
277 | } else { |
278 | Release(); |
279 | } |
280 | } catch (RuntimeException e) { |
281 | e.printStackTrace(); |
282 | } |
283 | } |
284 | |
285 | private boolean isReleaseThreadSet() { |
286 | if (m_release_thread == null) { |
287 | logger.log(Level.FINE, |
288 | "No OleThread associated with this apartment, " |
289 | + "free threaded release.", |
290 | m_apartment_defined_location); |
291 | } |
292 | |
293 | return m_release_thread != null; |
294 | } |
295 | |
296 | private void releaseOnReleaseThread() { |
297 | m_release_thread.simpleInvokeAndWait(new Runnable() { |
298 | public void run() { |
299 | // If Release() throws an Error, |
300 | // we could hang the finalizer thread. |
301 | // Thus, we make sure simpleInvokeAndWait |
302 | // throws something that invokeAndWait |
303 | // can handle. |
304 | try { |
305 | Release(); |
306 | } catch (Throwable t) { |
307 | throw new RuntimeException(t); |
308 | } |
309 | } |
310 | }); |
311 | } |
312 | |
313 | private void checkApartment() { |
314 | if (isApartmentChecked() && Thread.currentThread() != m_apartment) { |
315 | logger.log(Level.INFO, "Call on wrong thread detected.\n" |
316 | + " Apartment thread: " + m_apartment |
317 | + " actual thread: " + Thread.currentThread() + "\n" |
318 | + " Apartment defined at:", |
319 | m_apartment_defined_location); |
320 | |
321 | throw new COMException("Call on wrong thread. " + this |
322 | + ", expected=" + m_apartment + " actual=" |
323 | + Thread.currentThread(), |
324 | PlatformSDK.RPC_E_WRONG_THREAD); |
325 | } |
326 | } |
327 | |
328 | /** |
329 | * Only called from JNI |
330 | * |
331 | * @param iface_ptr |
332 | */ |
333 | private void internalSetIfacePtr(long iface_ptr) { |
334 | assert (isNullPtr()); |
335 | |
336 | internalOnSetInterface(); |
337 | m_iface_ptr = iface_ptr; |
338 | } |
339 | |
340 | /** |
341 | * Template method to setup apartment thread checking. Call: |
342 | * MoeSolInternal.recordApartment(this) to setup apartment thread |
343 | * checking. |
344 | */ |
345 | protected void internalOnSetInterface() { |
346 | assert (storeAllocationLocation()); |
347 | } |
348 | |
349 | /** |
350 | * Only called when assertions are turned on. This is reported as the |
351 | * allocation location if we get to finalize and have not called Release |
352 | * or we try to make a call from the wrong thread. |
353 | * |
354 | * @return true |
355 | */ |
356 | private boolean storeAllocationLocation() { |
357 | m_apartment_defined_location = new AllocationLocation(); |
358 | return true; |
359 | } |
360 | |
361 | /** |
362 | * Help prevent method/property name collisions. |
363 | */ |
364 | public static class MoeSolInternal { |
365 | /** |
366 | * Internal helper |
367 | * |
368 | * @param self |
369 | */ |
370 | public static void recordApartment(Jni self) { |
371 | self.recordApartment(); |
372 | } |
373 | |
374 | /** |
375 | * Internal helper |
376 | * |
377 | * @param self |
378 | */ |
379 | public static void checkApartment(Jni self) { |
380 | self.checkApartment(); |
381 | } |
382 | |
383 | /** |
384 | * Internal helper |
385 | * |
386 | * @param self |
387 | * @param clsid |
388 | * @param iid |
389 | */ |
390 | public static void coclassConstruct(Jni self, GUID clsid, GUID iid) { |
391 | self.coclassConstruct(clsid, iid, COM.CLSCTX_ALL); |
392 | } |
393 | |
394 | /** |
395 | * Internal helper |
396 | * |
397 | * @param self |
398 | * @param clsid |
399 | * @param iid |
400 | * @param clsctx |
401 | */ |
402 | public static void coclassConstruct(Jni self, GUID clsid, GUID iid, |
403 | int clsctx) { |
404 | self.coclassConstruct(clsid, iid, clsctx); |
405 | } |
406 | |
407 | /** |
408 | * Internal helper |
409 | * |
410 | * @param self |
411 | */ |
412 | public static void disableApartmentChecking(Jni self) { |
413 | self.disableApartmentChecking(); |
414 | } |
415 | |
416 | /** |
417 | * Internal helper |
418 | * |
419 | * @return unreleased count |
420 | */ |
421 | public static int getUnreleasedCount() { |
422 | return jni_getGlobalCount(); |
423 | } |
424 | |
425 | /** |
426 | * Internal helper |
427 | * |
428 | * @param self |
429 | * @param listener |
430 | * @param diid |
431 | */ |
432 | public static int addListener(Jni self, IUnknown listener, GUID diid) { |
433 | int cookie = self.addListener(listener, diid); |
434 | getCookieTable(self).addCookiePairToTable(listener, cookie); |
435 | return cookie; |
436 | } |
437 | |
438 | /** |
439 | * Internal helper |
440 | * |
441 | * @param self source |
442 | * @param listener to remove |
443 | * @param diid of interface |
444 | */ |
445 | public static void removeListener(Jni self, IUnknown listener, GUID diid) { |
446 | int cookie = getCookieTable(self).removeListenerAndCookieFromTable(self, listener); |
447 | self.removeListener(diid, cookie); |
448 | } |
449 | |
450 | /** |
451 | * @param source |
452 | * @return cookie table for this source |
453 | */ |
454 | private static ListenerCookieTable getCookieTable(IUnknown source) { |
455 | IUnknown.Jni source_jni = (IUnknown.Jni) source; |
456 | return EventSourceToListenerCookieTable.singleton() |
457 | .findOrMakeListenerCookieTable(source_jni); |
458 | } |
459 | |
460 | static void fixWarning(IUnknown.Jni jni) { |
461 | if (true) { |
462 | throw new IllegalStateException("never call"); |
463 | } |
464 | jni.internalSetIfacePtr(0); |
465 | } |
466 | } |
467 | |
468 | private void recordApartment() { |
469 | m_apartment = Thread.currentThread(); |
470 | assert (storeAllocationLocation()); |
471 | m_release_thread = OleThread.getCurrentOleThread(); |
472 | // These lines turned out to be too noisy even |
473 | // for known correct uses. Moved this warning to finalize. |
474 | // if (m_release_thread == null) { |
475 | // logger.log(Level.FINE, "No OleThread associated with this |
476 | // apartment."); |
477 | // } |
478 | } |
479 | |
480 | private boolean isApartmentChecked() { |
481 | return m_apartment != null; |
482 | } |
483 | |
484 | /** |
485 | * Turn off apartment model checking. |
486 | * |
487 | * @see com.moesol.bindings.platform_sdk.component_services.COM#disableApartmentChecking |
488 | */ |
489 | private void disableApartmentChecking() { |
490 | m_apartment = null; |
491 | } |
492 | |
493 | private boolean isNullPtr() { |
494 | return m_iface_ptr == 0; |
495 | } |
496 | |
497 | private void coclassConstruct(GUID clsid, GUID iid, int clsctx) { |
498 | int hr = jni_coclassConstruct(clsid._getStructureBytes(), iid |
499 | ._getStructureBytes(), clsctx); |
500 | if (HRESULT.FAILED(hr)) { |
501 | throw new COMException(hr); |
502 | } |
503 | } |
504 | |
505 | private native int jni_coclassConstruct(byte[] clsid, byte[] iid, |
506 | int clsctx); |
507 | |
508 | private static native int jni_QueryInterface(long iface_ptr, |
509 | byte[] interface_IID, IUnknown result); |
510 | |
511 | private static native int jni_Release(long iface_ptr); |
512 | |
513 | private native int jni_addListener(IUnknown sink, byte[] diid, |
514 | int[] out_cookie); |
515 | |
516 | private native int jni_removeListener(byte[] diid, int cookie); |
517 | |
518 | private static native int jni_getGlobalCount(); |
519 | |
520 | // Static State |
521 | private static Logger logger = Logger.getLogger(IUnknown.class |
522 | .getName()); |
523 | |
524 | /** |
525 | * The actual COM interface pointer, stored as a Java long. |
526 | */ |
527 | private long m_iface_ptr = 0; |
528 | |
529 | /** |
530 | * If non-null we can switch to this thread in finalize and release the |
531 | * object on the correct thread. |
532 | */ |
533 | private OleThread m_release_thread = null; |
534 | |
535 | // TODO combine m_release_thread and m_apartment to |
536 | // save on instance state (m_release_thread already has |
537 | // a reference to the java thread. |
538 | // TODO save below data items, see HANDLE for ideas. |
539 | // Or change HANDLE to use a field since the overhead |
540 | // for the HANDLE approach might be more significant. |
541 | |
542 | /** |
543 | * What thread is defined to be our apartment thread. |
544 | */ |
545 | private Thread m_apartment = null; |
546 | |
547 | /** |
548 | * Stack trace information for where this object defined the apartment |
549 | * it belongs to. |
550 | */ |
551 | private AllocationLocation m_apartment_defined_location = null; |
552 | } |
553 | |
554 | public static class Disp { |
555 | private static Disp s_instance = new Disp(); |
556 | public static Disp instance() { |
557 | return s_instance; |
558 | } |
559 | |
560 | protected Disp() { |
561 | addEntry(Integer.MIN_VALUE, (short)0, "_thunk_IUnknown_QueryInterface@12", "com_moesol_bindings.dll"); |
562 | addEntry(Integer.MIN_VALUE, (short)0, "_thunk_IUnknown_AddRef@4", "com_moesol_bindings.dll"); |
563 | addEntry(Integer.MIN_VALUE, (short)0, "_thunk_IUnknown_Release@4", "com_moesol_bindings.dll"); |
564 | } |
565 | |
566 | /* |
567 | * Normally this is never called since all of our subclasses are singletons. |
568 | * However, if the classloader is GC'ed this may get called |
569 | */ |
570 | protected void finalize() throws Throwable { |
571 | // TODO free any disp tables that where converted to native. |
572 | super.finalize(); |
573 | } |
574 | |
575 | protected void addEntry(int disp_id, short invkind, String name, String sig) { |
576 | m_entries.add(new DispatchJavaMethod(disp_id, invkind, name, sig)); |
577 | } |
578 | |
579 | public DispatchJavaMethod getEntryAt(int i) { |
580 | return (DispatchJavaMethod) m_entries.get(i); |
581 | } |
582 | |
583 | public int size() { |
584 | return m_entries.size(); |
585 | } |
586 | |
587 | public long getNativeEntries() { |
588 | return m_native_entries; |
589 | } |
590 | |
591 | void setNativeEntries(long native_entries) { |
592 | m_native_entries = native_entries; |
593 | // flush our java state, its be converted to a native pointer |
594 | m_entries = null; |
595 | } |
596 | |
597 | private Vector m_entries = new Vector(); |
598 | |
599 | private long m_native_entries = 0; |
600 | } |
601 | } |