Java로 클래스 간 객체 공유하기

앞 포스팅에서 직접 참조를 없애고 클래스 간 이벤트 통신 함으로써 의존도를 낮췄다. 이번 포스팅에서는 이벤트 통신시, 클래스 간 객체를 공유하는 방법에 대해 설명하려고 한다.

사실 이벤트 호출시 Object를 파라메터로 추가하여 던지고 받아도 되지만, 이렇게 할 경우 해당 이벤트를 구현하는 클래스가 아닌, 다른 이벤트를 구현하는 클래스에서는 필요할 때 값을 참조하지 못하는 딜레마가 발생한다.

이 딜레마를 해결하려면, 예를들어 “EVENT_A”라는 이벤트를 호출할 때 어떤 객체를 파라메터로 넘겨주면 “EVENT_A” 이벤트를 구현하는 클래스에서 해당 객체와 함께 “EVENT_B” 이벤트를 다시 호출하여야 한다.

SharedMemory 클래스를 아래와 같이 작성하면 언제 어디서든 원할 때 값을 참조할 수 있어서 위 딜레마를 더 쉽게 해결할 수 있다.

package org.silentsoft.io.memory;

import java.util.HashMap;

public final class SharedMemory {
	private static HashMap<String, Object> hashMap = new HashMap<String, Object>();

	public static synchronized HashMap<String, Object> getHashMap() {
		return hashMap;
	}

	public static synchronized void setHashMap(HashMap<String, Object> target) {
		hashMap = target;
	}
}

Event와 함께 사용하는 방법은 다음과 같다.

(코드는 앞 포스팅에서 작성했던 ListenerA, ListenerB를 조금 수정했다.)

package org.silentsoft.blog.event;

import java.util.ArrayList;

import org.silentsoft.io.memory.SharedMemory;
import org.silentsoft.io.event.EventHandler;
import org.silentsoft.io.event.EventListener;

public class ListenerA implements EventListener {
	public ListenerA() {
		EventHandler.addListener(this);
	}

	@Override
	public void onEvent(String event) {
		if (event.equals("EVENT_A")) {
			// do something here for EVENT_A event.
			System.out.println("I am A !");

			ArrayList<String> list = new ArrayList<String>();
			list.add("AA");
			SharedMemory.getHashMap().put("shared", list);
			list.add("BB");
		}
	}
}
package org.silentsoft.blog.event;

import org.silentsoft.io.memory.SharedMemory;
import org.silentsoft.io.event.EventHandler;
import org.silentsoft.io.event.EventListener;

public class ListenerB implements EventListener {
	public ListenerB() {
		EventHandler.addListener(this);
	}

	@Override
	public void onEvent(String event) {
		if (event.equals("EVENT_B")) {
			// do something here for EVENT_B event.
			System.out.println("I am B !");
			System.out.println(SharedMemory.getHashMap().get("shared")
					.toString());
		}
	}
}

Event 호출 코드

package org.silentsoft.blog.event;

import org.silentsoft.io.event.EventHandler;

public class Example {
	public static void main(String[] args) {
		ListenerA listenerA = new ListenerA();
		ListenerB listenerB = new ListenerB();

		EventHandler.callEvent(Example.class, "EVENT_A", false); // 동기 호출
		System.out.println("After introduce ListenerA");
		EventHandler.callEvent(Example.class, "EVENT_B"); // 비동기 호출
	}
}

ListenerA에서 “EVENT_A” 이벤트를 수신했을 때, ArrayList를 선언하고 “AA” 요소를 add한 뒤, SharedMemory에 넣었다. 이후, “BB” 요소를 add했다.

ListenerB에서 “EVENT_B”를 수신했을 때, ListenerA가 SharedMemory에 넣은 list를 출력한다. 이때, 출력값은 AA, BB이다.

예제를 위하여 이벤트 명과, SharedMemory의 Key값을 하드 코딩했지만, 영향도 분석 및 파악을 위하여 가능하면 이벤트 명은 EventConst 같은 클래스에, SharedMemory의 Key값은 KeyConst 같은 클래스를 만들어서 한 곳에 선언하는 것을 추천한다.

Java로 클래스 간 이벤트 통신하기

다른 클래스의 어떤 메소드를 수행하기 위해서 직접 참조를 하게 될 경우 확장성도 떨어지고, 해당 메소드의 파라메터가 변경될 경우, 참조하는 모든 클래스의 호출자를 바꿔주어야 한다. 그래서 클래스 간 직접 참조 없이 이벤트 통신을 한다는 것은 꽤나 큰 의미가 있는 일이다.

이벤트 통신의 근간은 이렇다.

이벤트를 받을 클래스에서 Listener 인터페이스를 구현한 뒤, 이벤트를 받겠다고 Handler로 등록을 하면, 다른 클래스에서 Handler를 통해 등록된 Listener 들을 호출하는 방식이다. 이때, 호출하는 방법은 동기 방식이냐, 비동기 방식이냐에 따라 2가지로 분류될 수 있다.

동기 방식으로 이벤트를 호출하게 될 경우, 먼저 등록된 Listener의 이벤트를 처리한 후, 그다음에 등록된 Listener의 이벤트를 처리하게 된다.

반면 비동기 방식으로 이벤트를 호출하게 될 경우, 먼저 등록된 Listener의 이벤트가 끝나든 말든 상관하지 않고, 계속해서 다음 Listener의 이벤트를 호출하게 된다.

다음은 각 방식의 이벤트 호출 사용 예이다.

동기(Synch) 방식 이벤트 호출 사용 예

이벤트 호출 후 다음 라인의 코드가 반드시 후처리로 진행되어야 하는 경우.

비동기(Asynch) 방식 이벤트 호출 사용 예

이벤트 호출 후 다음 라인의 코드가 이벤트와 상관없이 진행되어도 되는 비즈니스인 경우.

두 가지 방법 모두 상황에 따라서 쓰되, 특별한 이유(선처리-후처리 비즈니스)가 없는 한 비동기 방식을 권장한다. 왜냐하면 동기 방식은 먼저 등록된 Listener의 이벤트 수행이 끝난 뒤 다음 Listener의 이벤트를 처리하기 때문에, 비동기 방식에 비해 수행 속도가 느려지게 된다.

다음은 Event Listener와 Event Handler의 소스 코드이다.

package org.silentsoft.io.event;

public interface EventListener {
	public void onEvent(String event);
}
package org.silentsoft.io.event;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.silentsoft.io.event.EventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class EventHandler {

	private static final int MAX_THREAD_POOL = 5;

	private static final Logger LOGGER = LoggerFactory.getLogger(EventHandler.class);

	/**
	 * Note : ArrayList may occur ConcurrentModificationException so using
	 * CopyOnWriteArrayList for prevent Exception based on multi thread. Do not
	 * use below source code. private static List<EventListener> listeners = new
	 * ArrayList<EventListener>();
	 */
	private static List<EventListener> listeners = new CopyOnWriteArrayList<EventListener>();

	private static synchronized List<EventListener> getListeners() {
		return listeners;
	}

	public static synchronized void addListener(EventListener eventListener) {
		if (getListeners().indexOf(eventListener) == -1) {
			listeners.add(eventListener);
		}
	}

	public static synchronized void removeListener(EventListener eventListener) {
		if (getListeners().indexOf(eventListener) != -1) {
			listeners.remove(eventListener);
		}
	}

	public static synchronized void callEvent(final Class<?> caller, String event) {
		callEvent(caller, event, true);
	}

	public static synchronized void callEvent(final Class<?> caller, String event, boolean doAsynch) {
		if (doAsynch) {
			callEventByAsynch(caller, event);
		} else {
			callEventBySynch(caller, event);
		}
	}

	private static synchronized void callEventByAsynch(final Class<?> caller, final String event) {
		ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREAD_POOL);

		LOGGER.info("[Event occur : <{}> by <{}>]", new Object[] { event, caller.getName() });

		for (final EventListener listener : listeners) {
			executorService.execute(new Runnable() {
				public void run() {
					if (listener.getClass().getName().equals(caller.getName())) {
						LOGGER.info("[Event skip : <{}> by self <{}>]", new Object[] { event, caller.getName() });
					} else {
						LOGGER.info("[Event catch : <{}> by <{}>]", new Object[] { event, listener.getClass().getName() });

						listener.onEvent(event);
					}
				}
			});
		}

		executorService.shutdown();
	}

	private static synchronized void callEventBySynch(final Class<?> caller, final String event) {
		LOGGER.info("[Event occur : <{}> by <{}>]", new Object[] { event, caller.getName() });

		for (final EventListener listener : listeners) {
			if (listener.getClass().getName().equals(caller.getName())) {
				LOGGER.info("[Event skip : <{}> by self <{}>]", new Object[] { event, caller.getName() });
			} else {
				LOGGER.info("[Event catch : <{}> by <{}>]", new Object[] { event, listener.getClass().getName() });

				listener.onEvent(event);
			}
		}
	}
}

사용 방법은 다음과 같다.

ListenerA, ListenerB 클래스를 아래와 같이 선언하고

package org.silentsoft.blog.event;

import org.silentsoft.io.event.EventHandler;
import org.silentsoft.io.event.EventListener;

public class ListenerA implements EventListener {
	public ListenerA() {
		EventHandler.addListener(this);
	}

	@Override
	public void onEvent(String event) {
		if (event.equals("EVENT_A")) {
			// do something here for EVENT_A event.
			System.out.println("I am A !");
		}
	}
}
package org.silentsoft.blog.event;

import org.silentsoft.io.event.EventHandler;
import org.silentsoft.io.event.EventListener;

public class ListenerB implements EventListener {
	public ListenerB() {
		EventHandler.addListener(this);
	}

	@Override
	public void onEvent(String event) {
		if (event.equals("EVENT_B")) {
			// do something here for EVENT_B event.
			System.out.println("I am B !");
		}
	}
}

아래와 같이 이벤트를 호출하면 된다.

package org.silentsoft.blog.event;

import org.silentsoft.io.event.EventHandler;

public class Example {
	public static void main(String[] args) {
		ListenerA listenerA = new ListenerA();
		ListenerB listenerB = new ListenerB();

		EventHandler.callEvent(Example.class, "EVENT_A", false); // 동기 호출
		System.out.println("After introduce ListenerA");
		EventHandler.callEvent(Example.class, "EVENT_B"); // 비동기 호출
	}
}

EventHandler.callEvent() 메소드의

1번째 파라메터는 호출자이다. 누가 호출했는지 trace하기 위해서..

2번째 파라메터는 호출 할 이벤트 명이고,

3번째 파라메터에 따라 동기/비동기 방식으로 호출되고, 없을 경우 비동기로 호출한다.

앞에서 설명했지만, 동기 방식으로 호출할 경우 그다음 라인은 반드시 이벤트 처리가 끝나야만 수행된다.

예제를 위하여 이벤트 명을 하드 코딩했지만, 영향도 분석 및 파악을 위하여 이벤트는 가능하면 EventConst 같은 클래스를 만들어서 한 곳에 선언하는 것을 추천한다.

+ Bonus.

EventHandler 클래스에 주석을 남겼지만.. 멀티 스레드 환경에서 ConcurrentModificationException을 피하기 위해 ArrayList 대신 CopyOnWriteArrayList를 사용해야 한다.

Java로 트레이 아이콘 등록하기

Java에서 기본적으로 제공하는 SystemTray 클래스를 이용하면 아이콘을 등록거나,

메뉴를 등록하거나, 메세지를 띄우는건 의외로 간단하다.

void java.awt.SystemTray.add(TrayIcon trayIcon) throws AWTException

SystemTray.add() 메소드는 시스템 트레이에 트레이 아이콘을 등록해주는 메소드이다. 이 메소드를 사용해서 트레이 아이콘을 추가해보도록 하자.

우선, 한번 만들고 범용적으로 쓸 수 있도록 TrayIconHandler 클래스를 만들고,

아래와 같이 trayIcon 선언과 registerTrayIcon() 메소드를 작성하자.

private static TrayIcon trayIcon;

public static void registerTrayIcon(Image image, String toolTip, ActionListener action) {
	if (SystemTray.isSupported()) {
		if (trayIcon != null) {
			trayIcon = null;
		}
		trayIcon = new TrayIcon(image);
		trayIcon.setImageAutoSize(true);

		if (toolTip != null) {
			trayIcon.setToolTip(toolTip);
		}

		if (action != null) {
			trayIcon.addActionListener(action);
		}

		try {
			for (TrayIcon registeredTrayIcon : SystemTray.getSystemTray()
					.getTrayIcons()) {
				SystemTray.getSystemTray().remove(registeredTrayIcon);
			}

			SystemTray.getSystemTray().add(trayIcon);
		} catch (AWTException e) {
			LOGGER.error("I got catch an error during add system tray !", e);
		}
	} else {
		LOGGER.error("System tray is not supported !");
	}
}

[Parameters]

  • image : 시스템 트레이에 등록될 아이콘 이미지.
  • toolTip : 아이콘에 마우스를 올렸을 때 보여질 메세지.
  • action : 아이콘을 더블 클릭했을 때 실행 할 행동을 정의한 ActionListener.

사용 예는 아래와 같다.

public static void main(String[] args) {
	TrayIconHandler.registerTrayIcon(
		Toolkit.getDefaultToolkit().getImage("src/main/resources/icon/computer.png"),
		"Example",
		new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		}
	);
}

src/main/resources/icon 폴더 밑에 computer.png 아이콘 파일을 넘겨주고, 마우스로 올려질 때 보여질 “Example”, 그리고 더블 클릭했을 때 종료되는 코드이다.

이제 오른쪽 클릭했을 때 보여질 아이템을 추가하고, 아이템에 대한 액션을 정의해보자.

getPopupMenu(), add(), isRegistered(), isNotRegistered(), addItem() 총 4개의 메소드를 TrayIconHandler 클래스에 아래와 같이 정의한다.

private static PopupMenu getPopupMenu() {
	PopupMenu popupMenu = trayIcon.getPopupMenu();

	if (popupMenu == null) {
		popupMenu = new PopupMenu();
	}

	return popupMenu;
}

private static void add(MenuItem item) {
	if (isNotRegistered()) {
		return;
	}

	PopupMenu popupMenu = getPopupMenu();
	popupMenu.add(item);

	trayIcon.setPopupMenu(popupMenu);
}

public static boolean isRegistered() {
	return (trayIcon != null && getPopupMenu() != null) ? true : false;
}

public static boolean isNotRegistered() {
	return !isRegistered();
}

public static void addItem(String label, ActionListener action) {
	MenuItem menuItem = new MenuItem(label);
	menuItem.addActionListener(action);

	add(menuItem);
}

그리고 아래와 같이 addItem() 메소드를 호출하자.

public static void main(String[] args) {
	TrayIconHandler.registerTrayIcon(
		Toolkit.getDefaultToolkit().getImage("src/main/resources/icon/computer.png"),
		"Example",
		new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Open your application here.
			}
		}
	);
	TrayIconHandler.addItem("Exit", new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent e) {
			System.exit(0);
		}
	});
}

앞서 더블 클릭했을 때 수행하던 프로그램 종료 코드를 삭제하고, Exit 아이템을 클릭했을 때 종료하도록 변경했다.

트레이 아이콘 근처에 팝업 메세지를 띄우는 것도 가능하다.

아래와 같이 displayMessage() 메소드를 TrayIconHandler 클래스에 정의하자.

public static void displayMessage(String caption, String text, MessageType messageType) {
	if (isNotRegistered()) {
		return;
	}
	
	trayIcon.displayMessage(caption, text, messageType);
}

프로그램이 시작될 때 팝업 메세지를 보여주도록 displayMessage() 메소드를 호출하자.

public static void main(String[] args) {
	TrayIconHandler.registerTrayIcon(
		Toolkit.getDefaultToolkit().getImage("src/main/resources/icon/computer.png"),
		"Example",
		new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// Open your application here.
			}
		}
	);
	TrayIconHandler.addItem("Exit", new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent e) {
			System.exit(0);
		}
	});

	TrayIconHandler.displayMessage("Silentsoft", "Benefit the world !", MessageType.INFO);
}

이제 프로그램이 시작될 때 팝업 메세지가 보여진다.

MessageType은 INFO 이외에도 WARNING, ERROR, NONE을 사용할 수 있다.

TrayIconHandler.java

package org.silentsoft.core.tray;

import java.awt.AWTException;
import java.awt.CheckboxMenuItem;
import java.awt.Image;
import java.awt.Menu;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.TrayIcon.MessageType;
import java.awt.event.ActionListener;
import java.awt.event.ItemListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class TrayIconHandler {

	private static Logger LOGGER = LoggerFactory
			.getLogger(TrayIconHandler.class);

	private static TrayIcon trayIcon;

	private static PopupMenu getPopupMenu() {
		PopupMenu popupMenu = trayIcon.getPopupMenu();

		if (popupMenu == null) {
			popupMenu = new PopupMenu();
		}

		return popupMenu;
	}

	private static void add(MenuItem item) {
		if (isNotRegistered()) {
			return;
		}

		PopupMenu popupMenu = getPopupMenu();
		popupMenu.add(item);

		trayIcon.setPopupMenu(popupMenu);
	}

	private static void addToMenu(String menu, MenuItem item) {
		if (isNotRegistered()) {
			return;
		}

		if (isNotExistsMenu(menu)) {
			addMenu(menu);
		}

		for (int i = 0, j = getPopupMenu().getItemCount(); i < j; i++) {
			if (getPopupMenu().getItem(i) instanceof Menu) {
				Menu menuitem = (Menu) getPopupMenu().getItem(i);
				if (menuitem.getLabel().equals(menu)) {
					menuitem.add(item);

					getPopupMenu().insert(menuitem, i);

					break;
				}
			}
		}
	}

	public static boolean isRegistered() {
		return (trayIcon != null && getPopupMenu() != null) ? true : false;
	}

	public static boolean isNotRegistered() {
		return !isRegistered();
	}

	public static boolean isExistsMenu(String menu) {
		if (isNotRegistered()) {
			return false;
		}

		for (int i = 0, j = getPopupMenu().getItemCount(); i < j; i++) {
			if (getPopupMenu().getItem(i) instanceof Menu) {
				Menu item = (Menu) getPopupMenu().getItem(i);
				if (item.getLabel().equals(menu)) {
					return true;
				}
			}
		}

		return false;
	}

	public static boolean isNotExistsMenu(String menu) {
		return !isExistsMenu(menu);
	}

	public static void registerTrayIcon(Image image) {
		registerTrayIcon(image, null, null);
	}

	public static void registerTrayIcon(Image image, String toolTip) {
		registerTrayIcon(image, toolTip, null);
	}

	public static void registerTrayIcon(Image image, String toolTip,
			ActionListener action) {
		if (SystemTray.isSupported()) {
			if (trayIcon != null) {
				trayIcon = null;
			}

			trayIcon = new TrayIcon(image);
			trayIcon.setImageAutoSize(true);

			if (toolTip != null) {
				trayIcon.setToolTip(toolTip);
			}

			if (action != null) {
				trayIcon.addActionListener(action);
			}

			try {
				for (TrayIcon registeredTrayIcon : SystemTray.getSystemTray()
						.getTrayIcons()) {
					SystemTray.getSystemTray().remove(registeredTrayIcon);
				}

				SystemTray.getSystemTray().add(trayIcon);
			} catch (AWTException e) {
				LOGGER.error("I got catch an error during add system tray !", e);
			}
		} else {
			LOGGER.error("System tray is not supported !");
		}
	}

	public static void setToolTip(String toolTip) {
		if (isNotRegistered()) {
			return;
		}

		trayIcon.setToolTip(toolTip);
	}

	public static void setImage(Image image) {
		if (isNotRegistered()) {
			return;
		}

		trayIcon.setImage(image);
	}

	public static void displayMessage(String caption, String text,
			MessageType messageType) {
		if (isNotRegistered()) {
			return;
		}

		trayIcon.displayMessage(caption, text, messageType);
	}

	public static void addSeparator() {
		if (isNotRegistered()) {
			return;
		}

		getPopupMenu().addSeparator();
	}

	public static void addSeparator(String menu) {
		if (isNotRegistered()) {
			return;
		}

		for (int i = 0, j = getPopupMenu().getItemCount(); i < j; i++) {
			if (getPopupMenu().getItem(i) instanceof Menu) {
				Menu item = (Menu) getPopupMenu().getItem(i);
				if (item.getLabel().equals(menu)) {
					item.addSeparator();

					getPopupMenu().insert(item, i);

					break;
				}
			}
		}
	}

	public static void addMenu(String menu) {
		add(new Menu(menu));
	}

	public static void addItem(String label, ActionListener action) {
		MenuItem menuItem = new MenuItem(label);
		menuItem.addActionListener(action);

		add(menuItem);
	}

	public static void addCheckBox(String label, ItemListener action) {
		addCheckBox(label, false, action);
	}

	public static void addCheckBox(String label, boolean state,
			ItemListener action) {
		CheckboxMenuItem checkboxMenuItem = new CheckboxMenuItem(label, state);
		checkboxMenuItem.addItemListener(action);

		add(checkboxMenuItem);
	}

	public static void addItemToMenu(String menu, String label,
			ActionListener action) {
		MenuItem menuItem = new MenuItem(label);
		menuItem.addActionListener(action);

		addToMenu(menu, menuItem);
	}

	public static void addCheckBoxToMenu(String menu, String label,
			ItemListener action) {
		addCheckBoxToMenu(menu, label, false, action);
	}

	public static void addCheckBoxToMenu(String menu, String label,
			boolean state, ItemListener action) {
		CheckboxMenuItem checkboxMenuItem = new CheckboxMenuItem(label, state);
		checkboxMenuItem.addItemListener(action);

		addToMenu(menu, checkboxMenuItem);
	}
}

포스트에는 소개하지 않았지만, 위 TrayIconHandler 클래스에 구분선 추가, 메뉴 추가, 메뉴 안에 메뉴 혹은 아이템 추가, 체크박스를 추가할 수 있도록 구현해 놓았다.

코드가 너무 간단해서 별도의 주석은 없다.

Java에서 관리자 권한으로 실행하기

Java에서 관리자 권한으로 프로그램을 실행하거나, cmd 명령어를 실행하고 싶을 때가 종종 있다.

shell32.dll의 ShellExecuteEx 함수를 이용하면 Java에서 할 수 있다.

ShellExecuteEx

지정된 파일에 대해 작업을 수행해주는 함수.

그런데 이 녀석, DLL 함수이다. Java에서 DLL을 참조하기 위해서 JNA 라이브러리를 사용해야 한다.

JNA (Java Native Access)

Java에서 Native 영역으로 Access 시켜준다.

Maven으로 아래의 디펜던시를 추가하거나,

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>4.2.1</version>
</dependency>

혹은, 링크로 바로 다운받아서 프로젝트의 Build Path에 추가할 수도 있다.

JNA를 추가했다면, 이제 shell32.dll을 사용하기 위해서 인터페이스를 만들어야 한다. 정석대로라면 StdCallLibrary 인터페이스를 상속받아서 인터페이스를 만들어야 하지만, JNA에서는 Shell32 인터페이스를 지원하기때문에 Shell32 인터페이스를 사용 할 거다.

그런데 이 Shell32 인터페이스에는 쓰고싶은 ShellExecuteEx 함수가 정의되어 있지 않기 때문에 새로운 클래스 Shell32X를 만들어서 Shell32 인터페이스를 상속받은 다음 ShellExecuteEx 메소드를 정의해야 한다.

만약, JNA에서 지원하는 여러가지 인터페이스에 쓰고싶은 함수가 이미 정의되어 있다면, 굳이 새로운 인터페이스를 만들어서 상속받을 필요는 없다.

Shell32X.java

package org.silentsoft.core.util.elevator.extend;

import java.util.Arrays;
import java.util.List;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.Shell32;
import com.sun.jna.platform.win32.WinDef.HINSTANCE;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.platform.win32.WinReg.HKEY;
import com.sun.jna.win32.W32APIOptions;

public interface Shell32X extends Shell32 {
	Shell32X INSTANCE = (Shell32X) Native.loadLibrary("shell32", Shell32X.class, W32APIOptions.UNICODE_OPTIONS);

	int SW_HIDE = 0;
	int SW_MAXIMIZE = 3;
	int SW_MINIMIZE = 6;
	int SW_RESTORE = 9;
	int SW_SHOW = 5;
	int SW_SHOWDEFAULT = 10;
	int SW_SHOWMAXIMIZED = 3;
	int SW_SHOWMINIMIZED = 2;
	int SW_SHOWMINNOACTIVE = 7;
	int SW_SHOWNA = 8;
	int SW_SHOWNOACTIVATE = 4;
	int SW_SHOWNORMAL = 1;

	/** File not found. */
	int SE_ERR_FNF = 2;

	/** Path not found. */
	int SE_ERR_PNF = 3;

	/** Access denied. */
	int SE_ERR_ACCESSDENIED = 5;

	/** Out of memory. */
	int SE_ERR_OOM = 8;

	/** DLL not found. */
	int SE_ERR_DLLNOTFOUND = 32;

	/** Cannot share an open file. */
	int SE_ERR_SHARE = 26;

	int SEE_MASK_NOCLOSEPROCESS = 0x00000040;

	int ShellExecute(int i, String lpVerb, String lpFile, String lpParameters,
			String lpDirectory, int nShow);

	boolean ShellExecuteEx(SHELLEXECUTEINFO lpExecInfo);

	public static class SHELLEXECUTEINFO extends Structure {
		/*
		 * DWORD cbSize; ULONG fMask; HWND hwnd; LPCTSTR lpVerb; LPCTSTR lpFile;
		 * LPCTSTR lpParameters; LPCTSTR lpDirectory; int nShow; HINSTANCE
		 * hInstApp; LPVOID lpIDList; LPCTSTR lpClass; HKEY hkeyClass; DWORD
		 * dwHotKey; union { HANDLE hIcon; HANDLE hMonitor; } DUMMYUNIONNAME;
		 * HANDLE hProcess;
		 */

		public int cbSize = size();
		public int fMask;
		public HWND hwnd;
		public WString lpVerb;
		public WString lpFile;
		public WString lpParameters;
		public WString lpDirectory;
		public int nShow;
		public HINSTANCE hInstApp;
		public Pointer lpIDList;
		public WString lpClass;
		public HKEY hKeyClass;
		public int dwHotKey;

		/*
		 * Actually: union { HANDLE hIcon; HANDLE hMonitor; } DUMMYUNIONNAME;
		 */
		public HANDLE hMonitor;
		public HANDLE hProcess;

		protected List getFieldOrder() {
			return Arrays.asList(new String[] { "cbSize", "fMask", "hwnd",
					"lpVerb", "lpFile", "lpParameters", "lpDirectory", "nShow",
					"hInstApp", "lpIDList", "lpClass", "hKeyClass", "dwHotKey",
					"hMonitor", "hProcess", });
		}
	}
}

그리고 이 Shell32X 인터페이스를 호출해주는 Elevator 클래스를 만들자.

Elevator.java

package org.silentsoft.core.util.elevator.core;

import org.silentsoft.core.util.elevator.extend.Shell32X;
import org.silentsoft.core.util.elevator.extend.Shell32X.SHELLEXECUTEINFO;

import com.sun.jna.WString;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Kernel32Util;

public class Elevator {
	public static void executeAsAdmin(String command, String args) {
		SHELLEXECUTEINFO execInfo = new SHELLEXECUTEINFO();
		execInfo.lpFile = new WString(command);
		if (args != null) {
			execInfo.lpParameters = new WString(args);
		}
		execInfo.nShow = Shell32X.SW_SHOWDEFAULT;
		execInfo.fMask = Shell32X.SEE_MASK_NOCLOSEPROCESS;
		execInfo.lpVerb = new WString("runas");
		boolean result = Shell32X.INSTANCE.ShellExecuteEx(execInfo);

		if (!result) {
			int lastError = Kernel32.INSTANCE.GetLastError();
			String errorMessage = Kernel32Util.formatMessageFromLastErrorCode(lastError);
			throw new RuntimeException("Error performing elevation: " + lastError + ": " + errorMessage + " (apperror=" + execInfo.hInstApp + ")");
		}
	}
}

이제 Elevator 클래스를 이용하여 cmd 명령을 수행하거나, 프로그램을 실행할 수 있다.

cmd 명령을 수행하는 메소드이다.

public static void runCommand(String command) throws IOException {
	Runtime.getRuntime().exec("cmd /C " + command);
}

public static void runCommandAsAdmin(String command) {
	Elevator.executeAsAdmin("c:\\windows\\system32\\cmd.exe", "/C " + command);
}

프로그램을 실행하는 메소드이다.

public static void runProgram(String target, String args) throws IOException {
	args = (args == null) ? "" : args;
	Runtime.getRuntime().exec("cmd /C start " + target + " " + args);
}

public static void runProgramAsAdmin(String target, String args) {
	args = (args == null) ? "" : args;
	Elevator.executeAsAdmin("c:\\windows\\system32\\cmd.exe", "/C start " + target + " " + args);
}

이외에도 JNA를 이용하면 자바로 재미난 것을 많이 할 수 있다.

참조 글 :

Java에서 ocx 호출 삽질기 – 1부

Java로 개발하는 클라이언트 프로그램에서 ocx 파일을 참조해야 할 일이 생겼다. 그래서, Java와 Native간 다리(Bridge)를 놓아주는 라이브러리를 알아보던 중 Jacob과 j-interop을 찾을 수 있었고, 둘다 괜찮은 라이브러리지만 Jacob이 더 간결해보여서 COM 오브젝트의 CLSID로 ocx 호출에 성공했다.

Pagination


© 2017. All rights reserved.

Powered by Hydejack v7.5.1