모두의 프린터는 어떠한 경우에도 회원가입, 카드결제를 요구하지 않습니다.

Go언어에서 ShellExcute, ShellExcuteEx 함수 사용하기

Go언어에 exec 패키지를 통해 exec.Command().Run 이나 Start등으로 외부 명령을 실행할 수 있으나 이 함수들의 단점은 CreateProcess 기반인데다 부가적인 옵션이 거의 존재하지 않습니다. 예를들면 Run 함수의 경우에는 실행시킨 명령이 종료되기 전까진 대기를 해야 하지만 콘솔 어플리케이션이 아닌 윈도 어플리케이션을 호출하는 경우 일반적으로 콘솔기준에선 바로 실행이 끝난것으로 넘어오기 때문에 .. 조금 맞지 않는 부분이 생기구요..

예를들어 윈도 프로그래밍에선 ShellExcuteEx 를 사용한뒤에 WaitForSingleEvent 같은 함수로 해당 프로세스의 종료까지 대기가 가능하고 명령어를 open 이 아닌 runAs 같은걸 지정하면 관리자권한으로 실행등의 부가적인 효과를 얻을 수 있습니다.

아무래도 Go언어는 멀티플랫폼을 기준하다보니 모든 플랫폼의 세부적인 API에 대응되진 않는 편이라 그런데요..

그런 이유로 윈도에서 좀더 상세한 옵션을 포함하여 외부 프로세스를 실행하고자 할때는 ShellExcute, ShellExcuteEx함수를 사용하는 편입니다.

아래 코드는 제가 작성한 코드는 아닙니다.. 웹서핑 과정에서 .. 코드프로젝트 였나.. 스택오버플로였나;; 둘중 하나였던것 같은데.. 어떤 질문의 답변을 쫒아가다 보니 .. 나온 코드긴 한데 .. 작성자 정보를 찾게 되면 추가로 수정해 두도록 하겠습니다.

언제나 많은 능력자들 덕에 저같은 미천한 개발자가 도움을 받고 사는것 같네요.

Go
package main

import (
	"errors"
	"fmt"
	"os"
	"syscall"
	"unsafe"
)

var (
	modshell32         = syscall.NewLazyDLL("shell32.dll")
	procShellExecuteEx = modshell32.NewProc("ShellExecuteExW")
)

const (
	_SEE_MASK_DEFAULT            = 0x00000000
	_SEE_MASK_CLASSNAME          = 0x00000001
	_SEE_MASK_CLASSKEY           = 0x00000003
	_SEE_MASK_IDLIST             = 0x00000004
	_SEE_MASK_INVOKEIDLIST       = 0x0000000C
	_SEE_MASK_ICON               = 0x00000010
	_SEE_MASK_HOTKEY             = 0x00000020
	_SEE_MASK_NOCLOSEPROCESS     = 0x00000040
	_SEE_MASK_CONNECTNETDRV      = 0x00000080
	_SEE_MASK_NOASYNC            = 0x00000100
	_SEE_MASK_FLAG_DDEWAIT       = 0x00000100
	_SEE_MASK_DOENVSUBST         = 0x00000200
	_SEE_MASK_FLAG_NO_UI         = 0x00000400
	_SEE_MASK_UNICODE            = 0x00004000
	_SEE_MASK_NO_CONSOLE         = 0x00008000
	_SEE_MASK_ASYNCOK            = 0x00100000
	_SEE_MASK_NOQUERYCLASSSTORE  = 0x01000000
	_SEE_MASK_HMONITOR           = 0x00200000
	_SEE_MASK_NOZONECHECKS       = 0x00800000
	_SEE_MASK_WAITFORINPUTIDLE   = 0x02000000
	_SEE_MASK_FLAG_LOG_USAGE     = 0x04000000
	_SEE_MASK_FLAG_HINST_IS_SITE = 0x08000000
)

const (
	_ERROR_BAD_FORMAT = 11
)

const (
	_SE_ERR_FNF             = 2
	_SE_ERR_PNF             = 3
	_SE_ERR_ACCESSDENIED    = 5
	_SE_ERR_OOM             = 8
	_SE_ERR_DLLNOTFOUND     = 32
	_SE_ERR_SHARE           = 26
	_SE_ERR_ASSOCINCOMPLETE = 27
	_SE_ERR_DDETIMEOUT      = 28
	_SE_ERR_DDEFAIL         = 29
	_SE_ERR_DDEBUSY         = 30
	_SE_ERR_NOASSOC         = 31
)

type (
	dword     uint32
	hinstance syscall.Handle
	hkey      syscall.Handle
	hwnd      syscall.Handle
	ulong     uint32
	lpctstr   uintptr
	lpvoid    uintptr
)

// SHELLEXECUTEINFO struct
type _SHELLEXECUTEINFO struct {
	cbSize         dword
	fMask          ulong
	hwnd           hwnd
	lpVerb         lpctstr
	lpFile         lpctstr
	lpParameters   lpctstr
	lpDirectory    lpctstr
	nShow          int
	hInstApp       hinstance
	lpIDList       lpvoid
	lpClass        lpctstr
	hkeyClass      hkey
	dwHotKey       dword
	hIconOrMonitor syscall.Handle
	hProcess       syscall.Handle
}

// _ShellExecuteAndWait is version of ShellExecuteEx which want process
func _ShellExecuteAndWait(hwnd hwnd, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
	var lpctstrVerb, lpctstrParameters, lpctstrDirectory lpctstr
	if len(lpOperation) != 0 {
		lpctstrVerb = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation)))
	}
	if len(lpParameters) != 0 {
		lpctstrParameters = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters)))
	}
	if len(lpDirectory) != 0 {
		lpctstrDirectory = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory)))
	}
	i := &_SHELLEXECUTEINFO{
		fMask:        _SEE_MASK_NOCLOSEPROCESS,
		hwnd:         hwnd,
		lpVerb:       lpctstrVerb,
		lpFile:       lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))),
		lpParameters: lpctstrParameters,
		lpDirectory:  lpctstrDirectory,
		nShow:        nShowCmd,
	}
	i.cbSize = dword(unsafe.Sizeof(*i))
	return _ShellExecuteEx(i)
}

// _ShellExecuteNoWait is version of ShellExecuteEx which don't want process
func _ShellExecuteNowait(hwnd hwnd, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
	var lpctstrVerb, lpctstrParameters, lpctstrDirectory lpctstr
	if len(lpOperation) != 0 {
		lpctstrVerb = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation)))
	}
	if len(lpParameters) != 0 {
		lpctstrParameters = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters)))
	}
	if len(lpDirectory) != 0 {
		lpctstrDirectory = lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory)))
	}
	i := &_SHELLEXECUTEINFO{
		fMask:        _SEE_MASK_DEFAULT,
		hwnd:         hwnd,
		lpVerb:       lpctstrVerb,
		lpFile:       lpctstr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))),
		lpParameters: lpctstrParameters,
		lpDirectory:  lpctstrDirectory,
		nShow:        nShowCmd,
	}
	i.cbSize = dword(unsafe.Sizeof(*i))
	return _ShellExecuteEx(i)
}

// ShellExecuteEx is Windows API
func _ShellExecuteEx(pExecInfo *_SHELLEXECUTEINFO) error {
	ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(pExecInfo)))
	if ret == 1 && pExecInfo.fMask&_SEE_MASK_NOCLOSEPROCESS != 0 {
		s, e := syscall.WaitForSingleObject(syscall.Handle(pExecInfo.hProcess), syscall.INFINITE)
		switch s {
		case syscall.WAIT_OBJECT_0:
			break
		case syscall.WAIT_FAILED:
			return os.NewSyscallError("WaitForSingleObject", e)
		default:
			return errors.New("Unexpected result from WaitForSingleObject")
		}
	}
	errorMsg := ""
	if pExecInfo.hInstApp != 0 && pExecInfo.hInstApp <= 32 {
		switch int(pExecInfo.hInstApp) {
		case _SE_ERR_FNF:
			errorMsg = "The specified file was not found"
		case _SE_ERR_PNF:
			errorMsg = "The specified path was not found"
		case _ERROR_BAD_FORMAT:
			errorMsg = "The .exe file is invalid (non-Win32 .exe or error in .exe image)"
		case _SE_ERR_ACCESSDENIED:
			errorMsg = "The operating system denied access to the specified file"
		case _SE_ERR_ASSOCINCOMPLETE:
			errorMsg = "The file name association is incomplete or invalid"
		case _SE_ERR_DDEBUSY:
			errorMsg = "The DDE transaction could not be completed because other DDE transactions were being processed"
		case _SE_ERR_DDEFAIL:
			errorMsg = "The DDE transaction failed"
		case _SE_ERR_DDETIMEOUT:
			errorMsg = "The DDE transaction could not be completed because the request timed out"
		case _SE_ERR_DLLNOTFOUND:
			errorMsg = "The specified DLL was not found"
		case _SE_ERR_NOASSOC:
			errorMsg = "There is no application associated with the given file name extension"
		case _SE_ERR_OOM:
			errorMsg = "There was not enough memory to complete the operation"
		case _SE_ERR_SHARE:
			errorMsg = "A sharing violation occurred"
		default:
			errorMsg = fmt.Sprintf("Unknown error occurred with error code %v", pExecInfo.hInstApp)
		}
	} else {
		return nil
	}
	return errors.New(errorMsg)
}

%d