Создание и использование COM-объектов — задача, встречающаяся довольно часто. Достаточно сказать, что любой современный программный продукт Microsoft есть не что иное как набор подобных объектов, оформленных в виде компонент ActiveX и подключенных к пользовательскому интерфейсу (да простят меня читатели за это вынужденное упрощение!). Инструментов для разработки объектов COM также предостаточно. К примеру, это распространенные у нас компиляторы Microsoft Visual C++ и Borland C++Builder. Но это все для программирующих на языке Cи++. Borland Delphi позволяет писать COM-объекты на Паскале. Не забыты и любители Java. Microsoft Visual J++ дает возможность разработчику не только создавать, но и использовать COM-объекты из классов Java.

Cоздание сервера COM

Прежде всего воспользуемся вызовом мастера COM DLL, расположенного на закладке New?Components (она появляется при вызове команды File?New Project среды разработчика Visual J++). Для лучшего понимания рассмотрим сквозной пример COM-объекта, один метод которого запускает текстовый процессор Word, а другой завершает текущий сеанс Windows (аналог команды Log off). Сначала мне хотелось делать полный рестарт операционной системы, но, попробовав, я решил, что это слишком расточительно в плане расходования времени.

Рис. 1

Итак, создадим проект с именем COM_Object (рис. 1), внутри которого находится пустой класс-заготовка для будущего объекта. Хорошо бы, конечно, заменить его имя чем-то существенным, например CoMirPK. Для этого в окне проекта нужно щелкнуть на файле компоненты правой кнопкой мыши и выбрать команду переименования Rename. Несомненная неудача среды разработчика в том, что при смене имени файла не меняется название класса. Следовательно, это надо сделать самостоятельно. Зато внутри комментариев, которые видны в теле класса, имеется заготовка метода onCOMRegister(). Вы можете раскрыть ее и поместить внутрь код, вызываемый каждый раз при регистрации создаваемой компоненты в системе, и наоборот, при «выковыривании» из нее (рис. 2).

Рис. 2

Для каждого из двух методов создадим отдельный интерфейс, чтобы максимально использовать инструментарий среды, а планируемые действия занесем в список TaskList — эдакий электронный планировщик.

Сначала создадим первый интерфейс IRunWord2000, исходный текст которого весьма мал:

public interface IRunWord2000
{
  /**
   * Этот метод запускает текстовый процессор MS Word 2000
   */
  public abstract void runWord(String path);
}

Следом за ним нужно описать интерфейс ILogOff, также состоящий всего из нескольких строчек:

public interface ILogOff
{
  /**
   * Данный метод ?выгружает? текущего пользователя
   */
  public abstract void userLogOff();
}

Впрочем, малый размер позволяет описать их вручную. Однако полезно потренироваться, поэтому правой кнопкой мыши щелкаем на имени проекта в окне Project Explorer и из контекстного меню выбираем Add? AddClass?Interface (рис. 3).

Рис. 3

Сам же класс компоненты должен реализовать методы этих интерфейсов:

public final class CoMirPK implements IRunWord2000, ILogOff
Рис. 4

Когда имена их добавлены в описание класса, в окне Class Outline внутри структуры класса CoMirPK нужно найти папку с названием Implemented Interfaces. Вы увидите два значка интерфейсов, которые наш компонент реализует. Чтобы избежать лишней работы, щелкните на одном из них и выберите из контекстного меню команду Add Method Stubs. Благодаря ей в описании класса появится заготовка метода наследуемого интерфейса. Это и легче, чем ручное написание, и лучше с точки зрения защиты от ошибок. Повторите эти действия и для другого интерфейса.

В своей работе мы будем пользоваться вызовом программного интерфейса Win32, поэтому с помощью технологии J/Direct необходимо импортировать точку входа в искомую функцию ExitWindowsEx(). На линейке инструментов имеется специальный список полезных штучек Task List. Запустим из него команду J/Direct Call Builder, найдем в списке ExitWindowsEx() и нажмем кнопку Copy To Target (рис. 4).

В проекте появится новый класс-утилита Win32:

public class Win32
{
  /**
   * @dll.import(?USER32?,auto) 
   */
  public static native boolean ExitWindowsEx(int uFlags,
int dwReserved);
}

Внутри комментария к методу вы видите специальную директиву @dll.import, заставляющую виртуальную машину Java импортировать вызываемый метод из библиотеки USER32.

Остается дописать класс CoMirPK до «кондиционного» вида:

import com.ms.com.*;
import java.io.IOException;
/**
 * This class is designed to be packaged with a COM DLL
output format.
 * The class has no standard entry points, other than the
constructor.
 * Public methods will be exposed as methods on the default
COM interface.
 * @com.register (clsid=C0038C80-C7ED-4D8E-9E18-C90AA7EF1C67,
typelib=B343C87E-A9B5-43D0-9787-D56D4F962EAF)
 */
public final class CoMirPK implements IRunWord2000, ILogOff
{
	// TODO: Add additional methods and code here
	/**
	 * NOTE: To add auto-registration code, refer to the
documentation
	 * on the following method
	 *  public static void onCOMRegister(boolean
 unRegister) {}
	 */
  public void runWord(String path)
  {
    String commandLine = 
      ?c:Program FilesMicrosoft OfficeOffice
WINWORD.EXE?;
    if(path != null) commandLine = path;
    try
    {
      Runtime.getRuntime().exec(commandLine);
    }catch (IOException e)
    {
      com.ms.wfc.ui.MessageBox.show(
                  ?Не могу запустить Word!?, ?COM_Object?);
    }
  }
  public void userLogOff()
  {
    Win32.ExitWindowsEx(0,0);
  }
}

Уникальный идентификатор COM-класса задается директивой @com.register, расположенной непосредственно перед самым описанием класса компоненты. Директива import java.io.IOException нужна для вызова метода getRuntime().

Метод, вызывающий текстовый процессор Word, первоначально задает путь к Word 2000, так как он устанавливается по умолчанию. Тем не менее можно назначить альтернативный маршрут с помощью единственного параметра. При установке Word по умолчанию через этот параметр передается значение null. Дальше специальный класс-утилита Runtime вызовами getRuntime() и exec() запускает программу winword.exe, что и требуется.

Рис. 5

Метод перезапуска Windows включает в себя единственную строчку, в которой происходит вызов функции API ExitWindowsEx() с нулевыми параметрами, говорящими о том, что нужно лишь завершить текущий сеанс Windows. При другом значении первого параметра может произойти полная перезагрузка. Второй параметр всегда равен 0.

Примечание. Перед компиляцией загляните в опции проекта и найдите закладку COM Classes. Там вы сможете указать, какие классы вы хотите сделать объектами COM, а заодно установить некоторые опции создаваемой библиотеке типов (рис. 5).

Использование COM-объектов

Использование в среде Visual J++ созданного в ней же объекта не сложнее, чем любого другого COM-объекта, и строится на разборе библиотеки типов. Обратимся к примеру. Командой File?New Project?New?Applications доберемся до мастеров создания оконных приложений. Нас интересует Windows Application. После создания такого проекта в форму окна приложения помещаются две кнопки: «Запустить Word» и «Закончить сеанс Windows». Как вы, наверное, уже догадались, в обработчиках нажатий этих кнопок и будут вызываться методы объекта. Но об этом чуть позже.

Рис. 6

Главный вопрос: как обратиться к COM-объекту? Для этого мы должны создать «обертки», которые позволят обратиться из класса Java в сущность, нейтральную к языку программирования. И для этого в среде разработчика в меню Project есть специальная команда Add COM Wrapper. Вызовем ее и увидим, что в списке компонент COM есть и творение наших рук. Нужно только отметить его галочкой (рис. 6).

В результате будет сгенерирован новый пакет com_object, названный так по имени проекта, в котором COM-объект создавался. В этом пакете есть пара новых файлов. Вот первый из них (класс CoMirPK), являющийся шлюзом к двоичному коду компоненты:

package com_object;
import com.ms.com.*;
import com.ms.com.IUnknown;
import com.ms.com.Variant;
/** @com.class(classid=C0038C80-C7ED-4D8E-9E18-C90AA7EF1C67,
DynamicCasts) 
    @com.interface(iid=1127D7AF-52C0-4547-8C7D-4E492C5130D2,
thread=AUTO, type=DISPATCH) */
public class CoMirPK implements IUnknown,com.ms.com.
NoAutoScripting,com_object.CoMirPK_Dispatch
{
  /** @com.method(dispid=100, type=METHOD, name=?wait?,
returntype=VOID)
      @com.parameters([in,out,elementType=VARIANT,type=PTR]
Parameter0, [in,out,elementType=VARIANT,type=PTR] 
Parameter1, [type=VARIANT] return) */
  public native Variant wait(Variant Parameter0, Variant
Parameter1);
  /** @com.method(dispid=101, type=METHOD, name=?runWord?,
returntype=VOID)
      @com.parameters([in,type=STRING] Parameter0) */
  public native void runWord(String Parameter0);
  // hashCode UNMAPPABLE: Name is a keyword or conflicts
with another member.
  //  public native int hashCode();
  // toString UNMAPPABLE: Name is a keyword or conflicts 
with another member.
  //  public native String toString();
  // equals UNMAPPABLE: Name is a keyword or conflicts
with another member.
  //  public native boolean equals(Object Parameter0);
  /** @com.method(dispid=105, type=METHOD, name=?userLogOff?,
returntype=VOID)
      @com.parameters() */
  public native void userLogOff();
  // notify UNMAPPABLE: Name is a keyword or conflicts
with another member.
  //  public native void notify();
  // getClass UNMAPPABLE: Name is a keyword or conflicts
with another member.
  //  public native Object getClass();
  // notifyAll UNMAPPABLE: Name is a keyword or conflicts
with another member.
  //  public native void notifyAll();
  public static final com.ms.com._Guid iid = new com.ms.com.
_Guid((int)0x1127d7af, (short)0x52c0, (short)0x4547, 
(byte)0x8c, (byte)0x7d, (byte)0x4e, (byte)0x49, 
(byte)0x2c, (byte)0x51, (byte)0x30, (byte)0xd2);
  public static final com.ms.com._Guid clsid = new com.ms.com.
_Guid((int)0xc0038c80, (short)0xc7ed, (short)0x4d8e, 
(byte)0x9e, (byte)0x18, (byte)0xc9, (byte)0xa, 
(byte)0xa7, (byte)0xef, (byte)0x1c, (byte)0x67);
}

Второй интерфейс, CoMirPK_Dispatch, нужен для установки связи посредством автоматизации OLE и поддерживает вызов методов компоненты с помощью интерфейса IDispatch, за что и получил свое имя:

package com_object;
import com.ms.com.*;
import com.ms.com.IUnknown;
import com.ms.com.Variant;
// Dispatch-only interface CoMirPK_Dispatch
/** @com.interface(iid=1127D7AF-52C0-4547-8C7D-4E492C5130D2,
thread=AUTO, type=DISPATCH) */
public interface CoMirPK_Dispatch extends IUnknown
{
  /** @com.method(dispid=100, type=METHOD, name=?wait?,
returntype=VOID)
      @com.parameters([in,out,elementType=VARIANT,type=PTR]
Parameter0, [in,out,elementType=VARIANT,type=PTR] 
Parameter1, [type=VARIANT] return) */
  public Variant wait(Variant Parameter0, Variant
Parameter1);
  /** @com.method(dispid=101, type=METHOD, name=?runWord?,
returntype=VOID)
      @com.parameters([in,type=STRING] Parameter0) */
  public void runWord(String Parameter0);
    /** @com.method(dispid=105, type=METHOD, name=?userLogOff?,
 returntype=VOID)
      @com.parameters() */
  public void userLogOff();
  public static final com.ms.com._Guid iid = new com.ms.com.
_Guid((int)0x1127d7af, (short)0x52c0, (short)0x4547,
 (byte)0x8c, (byte)0x7d, (byte)0x4e, (byte)0x49, 
(byte)0x2c, (byte)0x51, (byte)0x30, (byte)0xd2);
}

Директивы, начинающиеся с @com, описывают мета-информацию о компоненте, взятую из библиотеки типов: числовой идентификатор метода, его параметры, имя и т. д.

Программа, которая использует COM-компоненту, обязательно должна содержать строку импорта всех классов пакета com_object:

import com_object.*;

Одно из полей класса программы описывает ссылку на интерфейс автоматизации:

. . .
CoMirPK_Dispatch component;
. . .

а значение присваивается ей внутри конструктора класса:

. . .
// TODO: Add any constructor code after initForm call
    component = new CoMirPK();
. . .

Теперь перейдем к обработчикам, из которых вызываются методы COM-объекта. Тот, что вызывается в ответ на нажатие кнопки «Запустить Word», выглядит следующим образом:

  private void button1_click(Object source, Event e)
  {
    // Запустить Word
    component.runWord(null);
  }

Приведем исходный текст обработчика нажатия кнопки «Закончить сеанс Windows»:

  private void button2_click(Object source, Event e)
  {
    // Закончить сеанс Windows
    component.userLogOff();
  }

Попробуйте готовую тестовую программу в деле. Заодно испытайте компоненту в других средах, например, в Borland C++Builder.