The COM model for Threading differs from the Java model. In COM, each component can declare whether or not it support multi-threading. You can find some basic information about COM threading at: (用于线程的COM模型不同于Java模型。在COM中,每个组件都可以声明其是否支持多线程。您可以在以下位置找到有关COM线程的一些基本信息:)
http://www.execpc.com/~gopalan/com/com_threading.html (HTTP://呜呜呜.exec PC.com/~GOP Alan/com/com_threading.HTML)
www.microsoft.com/msj/0297/apartment/apartment.htm (呜呜呜.Microsoft.com/买手机/0297/apartment/apartment.htm)
http://www.cswl.com/whiteppr/white/multithreading.html (HTTP://呜呜呜.撑死我了.com/white ppr/white/multi threading.HTML)
(我从未见过如此骚的翻译)
The term Single Threaded Apartment (STA) refers to a thread where all COM objects created in that thread are single-threaded. This can manifest itself in two ways:
Either all calls into that component are made from the same thread that created the component
OR any call that is made from another thread gets serialized by COM. This serialization of calls is done by using a Windows message loop and posting messages to a hidden window (I'm not kidding). The way COM achieves this is by requiring any other thread to make calls through a local Proxy object rather than the original object (more on this when we discuss the JACOB DispatchProxy class). (术语“单线程单元(STA)”是指在该线程中创建的所有COM对象都是单线程的线程。这可以通过两种方式体现出来:对组件的所有调用均由创建该组件的同一线程进行,或者由另一线程进行的任何调用均由COM进行序列化。通过使用Windows消息循环并将消息发布到隐藏的窗口中,可以完成呼叫的序列化(我不是在开玩笑)。 COM实现此目的的方法是要求任何其他线程通过本地Proxy对象而不是原始对象进行调用(在我们讨论JACOB DispatchProxy类时,将对此进行更多介绍)。)
What does this mean for a Java application? If you are using a component that declares itself as ThreadingModel "Apartment" (you can find this out by looking in the registry under its CLSID), and you plan to create, use and destroy this component in one thread - then you are following the rules of an STA and you can declare the thread as an STA thread. (这对于Java应用程序意味着什么?如果您使用的是一个声明为ThreadingModel“ Apartment”的组件(可以通过在其CLSID下的注册表中找到它),并且计划在一个线程中创建,使用和销毁该组件-那么您将遵循您可以将线程声明为STA线程。)
On the other hand, if you need to make method calls from another thread (e.g. in a servlet) then you have a few choices. Either you create the component in its own STA, by extending com.jacob.com.STA
, and use the com.jacob.com.DispatchProxy
class to pass the Dispatch pointer between threads, or you can declare your thread as an MTA thread. In that case, COM will make the cross-thread calls into the STA that is running your component. If you create an Apartment threaded component in the MTA, COM will automatically create an STA for you and put your component in there, and then marshall all the calls. (另一方面,如果您需要从另一个线程(例如,在servlet中)进行方法调用,则可以选择几种方法。您可以通过扩展com.jacob.com.STA在其自己的STA中创建组件,然后使用com.jacob.com.DispatchProxy类在线程之间传递Dispatch指针,也可以将线程声明为MTA线程。在这种情况下,COM将对运行您的组件的STA进行跨线程调用。如果您在MTA中创建一个Apartment线程组件,COM将自动为您创建一个STA并将您的组件放入其中,然后将所有调用编组。)
This brings us to the notion of a Main STA. COM requires that if there is any Apartment threaded component in your application, then the first STA created is tagged as the Main STA. COM uses the Main STA to create all the Apartment threaded components that are created from an MTA thread. The problem is that if you have already created an STA, then COM will pick that as the Main STA, and if you ever exit that thread - the whole application will exit. (这使我们想到了主要STA的概念。 COM要求,如果您的应用程序中有任何Apartment线程组件,则将创建的第一个STA标记为Main STA。 COM使用主STA创建从MTA线程创建的所有Apartment线程组件。问题是,如果您已经创建了一个STA,那么COM将把它选为主STA,并且如果您退出该线程-整个应用程序将退出。)
Up until version 1.7 of JACOB, there was only one model available in JACOB: (直到JACOB 1.7版,JACOB中只有一种模型可用:)
The reason for the change in default was that tagging a Java thread as an STA can cause problems. Any Java Swing application, as well as servlets and applets need to be able to make calls from multiple threads. If you try to make COM method calls across STA threads - it will fail! (更改默认设置的原因是,将Java线程标记为STA可能会引起问题。任何Java Swing应用程序以及servlet和applet都必须能够从多个线程进行调用。如果您尝试跨STA线程进行COM方法调用-它将失败!)
In most cases, the default chosen by JACOB 1.6 (MTA) works fine, however there are some notable exceptions that have caused people grief. One such exception is in the case of MAPI. It turns out that if you try to create a MAPI object from an MTA thread - it simply fails and exits. This has caused some people to recompile JACOB 1.6 back with the STA default. (在大多数情况下,JACOB 1.6(MTA)选择的默认值可以正常工作,但是有些显着的例外已引起人们的痛苦。 MAPI就是这种例外之一。事实证明,如果您尝试从MTA线程创建MAPI对象-它只会失败并退出。这导致某些人使用STA默认值重新编译JACOB 1.6。)
There is another problem with MTA threads: when you are using Apartment threaded components, we already noted that COM will create the components in the Main STA. If one doesn't exist, COM will create it. However, this means that all Apartment threaded components will be created in the same STA. This creates a bottleneck, and a dependency between unrelated components. Also, if that STA exits, then all components are destroyed and the application will likely crash. (MTA线程还有另一个问题:使用Apartment线程组件时,我们已经注意到COM将在主STA中创建组件。如果不存在,COM将创建它。但是,这意味着将在同一STA中创建所有Apartment线程组件。这会造成瓶颈,并在不相关的组件之间产生依赖性。同样,如果该STA退出,则所有组件都将被破坏,应用程序很可能崩溃。)
In Version 1.7 we have added finer grained control to allow the Java programmer to control how COM creates its components. Unfortunately, this means that you need to have a pretty good understanding of the dark and mystical subject of COM Apartments. There are a few different cases you need to consider: (在1.7版中,我们添加了更细粒度的控件,以允许Java程序员控制COM如何创建其组件。不幸的是,这意味着您需要对COM Apartments的黑暗和神秘主题有一个很好的了解。您需要考虑几种不同的情况:)
If you simply run code that was created in Version 1.6 and ignore the COM threading issue, then you will get the same behavior as in 1.6: Each java thread will be an MTA thread, and all Apartment threaded components will be created by COM in its own Main STA. This typically works for most applications (exceptions noted above). (如果仅运行在1.6版中创建的代码,而忽略COM线程问题,那么您将获得与1.6中相同的行为:每个Java线程都是一个MTA线程,并且所有Apartment线程组件都将由COM在其内部创建。自己的主要STA。这通常适用于大多数应用程序(上述例外情况)。)
To declare an MTA thread use the following template:
(要声明MTA线程,请使用以下模板:)
ComThread.InitMTA();
...
...
ComThread.Release();
If you want JACOB to create its own Main STA (rather than having COM choose an STA for you), then you should use: (如果您希望JACOB创建自己的Main STA(而不是让COM为您选择STA),则应使用:)
Thread 1:
ComThread.InitMTA(true); // a true tells JACOB to create a Main STA
...
...
ComThread.Release();
...
Thread 2:
ComThread.InitMTA();
...
...
ComThread.Release();
...
...
ComThread.quitMainSTA();
In this case, you can also create the Main STA explicitly: (在这种情况下,您还可以显式创建Main STA:)
ComThread.startMainSTA();
...
...
Thread 1:
ComThread.InitMTA();
...
...
ComThread.Release();
...
Thread 2:
ComThread.InitMTA();
...
...
ComThread.Release();
...
...
ComThread.quitMainSTA();
In the latter case, all Apartment threaded components will be created in JACOB's main STA. This still has all the problems of components sharing the same Main STA and creating a bottleneck. To avoid that, you can also create STA threads yourself: (在后一种情况下,所有Apartment线程组件都将在JACOB的主STA中创建。这仍然存在组件共享同一主STA并造成瓶颈的所有问题。为了避免这种情况,您还可以自己创建STA线程:)
ComThread.startMainSTA();
...
...
Thread 1:
ComThread.InitSTA();
...
...
ComThread.Release();
...
Thread 2:
ComThread.InitMTA();
...
...
ComThread.Release();
...
...
ComThread.quitMainSTA();
In this example, thread 1 is an STA and thread 2 is an MTA. You could omit the call to ComThread.startMainSTA(), but if you do, then COM will make the first STA your main one, and then if you exit that thread, the application will crash. (在此的示例线程1是STA,线程2是MTA。您可以省略对ComThread.startMainSTA()的调用,但是如果这样做,则COM将使第一个STA成为您的主STA,然后,如果退出该线程,应用程序将崩溃。)
Actually, Thread 1 is almost an STA. It's lacking a windows message loop. So, this type of STA is fine as long as you are creating a component and using it in the same thread, and not makind event callbacks. (实际上,线程1几乎是STA。它缺少Windows消息循环。因此,只要您正在创建组件并在同一线程中使用它,而不使用makind事件回调,则这种类型的STA很好。)
If you want to create an true STA where you can create a component and then let other threads call methods on it, then you need a windows message loop. JACOB provides a class called: com.jacob.com.STA
which does exactly this.
(如果要创建一个真正的STA,可以在其中创建组件,然后让其他线程在其上调用方法,则需要Windows消息循环。 JACOB提供了一个名为com.jacob.com.STA的类,该类正是这样做的。)
public class com.jacob.com.STA extends java.lang.Thread
{
public com.jacob.com.STA();
public boolean OnInit(); // you override this
public void OnQuit(); // you override this
public void quit(); // you can call this from ANY thread
}
The STA class extends java.lang.Thread
and it provides you with two methods that you can override: OnInit
and OnQuit
. These methods are called from the thread's run
method so they will execute in the new thread. These methods allow you to create COM components (Dispatch objects) and release them. To create an STA, you subclass it and override the OnInit. (STA类扩展了java.lang.Thread,它为您提供了两个可以重写的方法:OnInit和OnQuit。这些方法是从线程的run方法调用的,因此它们将在新线程中执行。这些方法允许您创建COM组件(Dispatch对象)并释放它们。要创建STA,请对其进行子类化并覆盖OnInit。)
The quit
method is the only other method that can be called from any thread. This method uses the Win32 function PostThreadMessage
to force the STA's windows message loop to exit, thereby terminating the thread. (quit方法是可以从任何线程调用的唯一其他方法。此方法使用Win32函数PostThreadMessage强制退出STA的Windows消息循环,从而终止线程。)
You will then need to make calls into the component that is running in the STA thread. If you simply try to make calls from another thread on a Dispatch object created in the STA thread, you will get a COM Exception. For more details see: Don Box 'Effective COM' Rule 29: Don't Access raw interface pointers across apartment boundaries. (然后,您需要调用STA线程中正在运行的组件。如果仅尝试从另一个线程在STA线程中创建的Dispatch对象上进行调用,则将获得COM异常。有关更多详细信息,请参见:Don Box'Effective COM'Rule 29:不要跨单元边界访问原始接口指针。)
Since you cannot call methods directly on a Dispatch object created in another STA JACOB provides a method for the class that created the Dispatch object to marshal it to your thread. This is done via the (由于您不能直接在另一个STA中创建的Dispatch对象上调用方法,因此JACOB为创建Dispatch对象的类提供了一种将其编组到线程中的方法。这是通过)
com.jacob.com.DispatchProxy
class. (类。)
public class DispatchProxy extends JacobObject {
public DispatchProxy(Dispatch);
public Dispatch toDispatch();
public native void release();
public void finalize();
}
This class works as follows: the thread that created the Dispatch object constructs an instance of DispatchProxy(Dispatch) with the Dispatch as a parameter. This instance can then be accessed from another thread, which will invoke its toDispatch
method proxy as if it were local to your thread. COM will do the inter-thread marshalling transparently. (此类的工作方式如下:创建Dispatch对象的线程以Dispatch作为参数构造DispatchProxy(Dispatch)的实例。然后可以从另一个线程访问此实例,该线程将调用其toDispatch方法代理,就好像它在您的线程本地一样。 COM将透明地进行线程间编组。)
The following example is part of samples/test/ScriptTest2.java in the JACOB distribution. It shows how you can create the ScriptControl in one STA thread and make method calls on it from another:
(以下示例是JACOB发行版中samples / test / ScriptTest2.java的一部分。它显示了如何在一个STA线程中创建ScriptControl并从另一个线程对其进行方法调用:)
import com.jacob.com.*;
import com.jacob.activeX.*;
class ScriptTest2 extends STA
{
public static ActiveXComponent sC;
public static Dispatch sControl = null;
public static DispatchProxy sCon = null;
public boolean OnInit()
{
try
{
System.out.println("OnInit");
System.out.println(Thread.currentThread());
String lang = "VBScript";
sC = new ActiveXComponent("ScriptControl");
sControl = (Dispatch)sC.getObject();
// sCon can be called from another thread
sCon = new DispatchProxy(sControl);
Dispatch.put(sControl, "Language", lang);
return true;
}
catch (Exception e)
{
e.printStackTrace();
return false;
}
}
public void OnQuit()
{
System.out.println("OnQuit");
}
public static void main(String args[]) throws Exception
{
try {
ComThread.InitSTA();
ScriptTest2 script = new ScriptTest2();
Thread.sleep(1000);
// get a thread-local Dispatch from sCon
Dispatch sc = sCon.toDispatch();
// call a method on the thread-local Dispatch obtained
// from the DispatchProxy. If you try to make the same
// method call on the sControl object - you will get a
// ComException.
Variant result = Dispatch.call(sc, "Eval", args[0]);
System.out.println("eval("+args[0]+") = "+ result);
script.quit();
System.out.println("called quit");
} catch (ComException e) {
e.printStackTrace();
}
finally
{
ComThread.Release();
}
}
}
You can try to modify the Dispatch.call
invocation in the main thread to use sControl
directly, and you will see that it fails. Notice that once we construct the ScriptTest2 object in the main thread, we sleep for a second to allow the other thread time to initialize itself. (您可以尝试在主线程中修改Dispatch.call调用以直接使用sControl,您会发现它失败。请注意,一旦在主线程中构造了ScriptTest2对象,我们就会睡一秒钟,以允许其他线程有时间对其进行初始化。)
The STA thread calls sCon = new DispatchProxy(sControl);
to save a global reference to the DispatchProxy that represents the sControl
object. The main thread then calls: Dispatch sc = sCon.toDispatch();
to get a local Dispatch proxy out of the DispatchProxy object. (STA线程调用sCon = new DispatchProxy(sControl);保存对表示sControl对象的DispatchProxy的全局引用。然后,主线程调用:Dispatch sc = sCon.toDispatch();从DispatchProxy对象中获取本地Dispatch代理。)
At most one(!) thread can call toDispatch(), and the call can be made only once. This is because a IStream object is used to pass the proxy, and it is only written once and closed when you read it. If you need multiple threads to access a Dispatch pointer, then create that many DispatchProxy objects. For more details please refer to the Don Box reference above. (最多一个(!)线程可以调用toDispatch(),并且该调用只能进行一次。这是因为IStream对象用于传递代理,并且只写入一次并在读取时关闭。如果您需要多个线程来访问Dispatch指针,则创建那么多个DispatchProxy对象。有关更多详细信息,请参阅上面的Don Box参考。)
com.jacob.com.STA
class as outlined above. (如果要允许其他线程调用的STA线程,请使用上面概述的com.jacob.com.STA类。)There are 3 examples in the samples/test directory that demonstrate these cases: (示例/测试目录中有3个示例演示了这些情况:)
ScriptTest.java - creates an STA for the ScriptControl component and runs all its method calls from that STA. (ScriptTest.java-为ScriptControl组件创建一个STA,并从该STA运行其所有方法调用。)
ScriptTest2.java - creates a separate STA thread, and makes method calls into the component from another thread using DispatchProxy. (ScriptTest2.java-创建一个单独的STA线程,并使用DispatchProxy从另一个线程对组件进行方法调用。)
ScriptTest3.java - creates a separate MTA thread, and makes method calls into the component from another MTA thread. This is simpler than ScriptTest2 for most applications. (ScriptTest3.java-创建一个单独的MTA线程,并从另一个MTA线程对该组件进行方法调用。对于大多数应用程序,这比ScriptTest2简单。)
ComThread.InitSTA()
or ComThread.InitMTA()
on it, then the first time your java code creates a JacobObject, it will try to register itself with the ROT, and when it sees that the current thread is not initialized, it will initialize it as MTA. This means that the code to do this is no longer inside the native jni code - it is now in the com.jacob.com.ROT
class. For more details on the ROT, see the Object Lifetime document. (在大多数情况下,如果需要从多个线程进行方法调用,则可以简单地使用MTA线程,并允许COM在主STA中创建组件。如果您对COM有足够的了解,并且知道MTA解决方案何时会失败或存在其他缺点,则仅应创建自己的STA和DispatchProxy。 samples / test目录中有3个示例演示了这些情况:ScriptTest.java-为ScriptControl组件创建一个STA,并从该STA运行其所有方法调用。 ScriptTest2.java-创建一个单独的STA线程,并使用DispatchProxy从另一个线程对组件进行方法调用。 ScriptTest3.java-创建一个单独的MTA线程,并从另一个MTA线程对该组件进行方法调用。对于大多数应用程序,这比ScriptTest2简单。默认线程模型如果您创建一个新线程,并且不对其调用ComThread.InitSTA()或ComThread.InitMTA(),那么您的Java代码首次创建JacobObject时,它将尝试向ROT注册自己,当看到当前线程未初始化时,它将初始化为MTA。这意味着执行此操作的代码不再位于本机jni代码内-现在位于com.jacob.com.ROT类中。有关ROT的更多详细信息,请参见对象生存期文档。)