태그 보관물: C#

C# 서비스 프로그램 만들기

개요

여러가지 이유로 프로그램을 컴퓨터가 시작되면서 실행해야 하는 경우가 있습니다. 프로그램을  시작 프로그램에 등록해서 실행되도록 할 수 있습니다. 이 방법이 아니고 필요에 따라서 서비스 프로그램으로 제작해야 하는 경우가 있습니다. 이번 포스트에서는 C# 서비스 프로그램 으로 제작하는 방법을 알아보도록 하겠습니다.

서비스 프로그램은 실행파일로 컴파일되도 바로 실행되지 않습니다. 실행하면 다음과 같은 오류가 발생합니다.

서비스 프로그램을 바로 실행한 경우

서비스 프로그램을 바로 실행한 경우

installutil 유틸리티를 이용하여 등록해야 합니다. 이 유틸리티는 다음의 경로에 있습니다. 프롬프트는 관리자 모드로 실행 후 명령을 수행해야 제대로 설치됩니다. 그렇지 않으면 “System.Security.SecurityException: 소스를 찾을 수 없습니다. 일부 또는 전체 이벤트 로그를 검색하지 못했습니다. 액세스할 수 없는 로그: Security.” 이런식의 오류가 발생합니다.

C:\Windows\Microsoft.NET\Framework\v4.0.30319\installutil

다음의 명령으로 등록하면 되고 /u 옵션을 주면 등록을 해제할 수 있습니다.

C:\Windows\Microsoft.NET\Framework\v4.0.30319\installutil.exe C:\경로\서비스프로그램.exe

서비스 프로그램 프로젝트 생성

프로젝트 생성을 선택하고 다음의 Windows 서비스 템플릿을 선택합니다.

Windows 서비스 템플릿 선택

Windows 서비스 템플릿 선택

서비스 프로그램에서 처리할 부분을 작업하고 빌드합니다. 생성된 exe파일을 위의 명령으로 등록해보면 정상적으로 되지 않는 것을 알수 있습니다. “D:\temp\WindowsService1\WindowsService1\bin\Release\WindowsService1.exe 어셈블리에서 RunInstallerAttribute.Yes 특성을 포함하는 공용 설치 관리자를 찾을 수 없습니다.
설치 관리자가 없으므로 InstallState 파일을 제거합니다.” 별도의 설치관련 클래스를 추가해야 합니다.

설치되지 않을때의 메시지

설치되지 않을때의 메시지

설치관련 클래스추가

아래와 같이 클래스를 추가합니다. 여기에서 클래스는 public으로 선언해야 정상적으로 서비스로 설치됩니다.

설치 관리자 클래스 추가

설치 관리자 클래스 추가

클래스를 추가한 후 System.Configuration.Install 어셈블리를 참조 추가합니다.

System.Configuration.Install 어셈블리를 참조 추가

System.Configuration.Install 어셈블리를 참조 추가

다음과 같이 생성자를 추가하고 서비스의 이름과 설명등을 적절하게 입력합니다. RunInstallerAttribute(true) 속성을 추가하고 WindowsServiceInstaller 클래스는 Installer 클래스를 상속하도록 합니다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.ServiceProcess;
using System.Text;

namespace WindowsService1
{
	[RunInstallerAttribute(true)]
	public class WindowsServiceInstaller: Installer
	{
		public WindowsServiceInstaller()
		{
			var serviceProcessInstaller = new ServiceProcessInstaller();
			var serviceInstaller = new ServiceInstaller();

			serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
			serviceProcessInstaller.Username = null;
			serviceProcessInstaller.Password = null;

			serviceInstaller.Description = "Mail monitoring service";
			serviceInstaller.DisplayName = "Mail monitor";
			serviceInstaller.ServiceName = "Mail monitoring";
			serviceInstaller.StartType = ServiceStartMode.Automatic;

			this.Installers.Add(serviceProcessInstaller);
			this.Installers.Add(serviceInstaller);
		}
	}
}

빌드한 후 위의 등록 명령을 다시 실행하면 정상적으로 등록됨을 알 수 있습니다.

정상적으로 등록된 서비스 프로그램

정상적으로 등록된 서비스 프로그램

이상으로 .NET 기반 C# 으로 서비스 프로그램을 만드는 방법을 알아보았습니다.

 

 

C# 에서 DLL 함수 호출

개요

프로젝트를 진행하다 보면 외부 시스템이나 장비와 연동해야 하는 경우가 있습니다. DLL 파일과 부실한 문서만 전달해 주고 ‘알아서 해라!’ 라는 식으로 진행되는 경우가 많습니다. 이런 경우 C# 에서 DLL 함수 호출 을 해야 합니다. 이런 DLL 들은 대부분 .NET 기반에서 만들어진 것이 아닙니다. .NET 기반에서 만들어진 경우는 참조해서 사용하면 됩니다. .NET 기반이 아닌 DLL을 참조하려고 하면 다음과 같은 오류가 발생합니다.

.NET 기반이 아닌 DLL 을 참조하면 발생하는 오류

.NET 기반이 아닌 DLL 을 참조하면 발생하는 오류

준비

WebForm, WinForm 모두 방식은 동일합니다. WebForm 기준으로 살펴보도록 하겠습니다. 사용할 DLL은 GetIPAddress 메소드만 존재합니다. 호출하면 IP 주소와 PC 이름을 변수에 담아 전달해 주는 기능을 합니다.

먼저 코드 상단에 다음과 같이 DLL Import 부분과 함수정의 부분을 작성합니다.

[DllImport("yourdll.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
extern static void GetIPAddress([Out, MarshalAs(UnmanagedType.LPArray)] byte[] ipAddress, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pcName);
extern static void GetIPAddress(byte[] ipAddress, byte[] pcName);

CallingConvention = CallingConvention.StdCall 이 부분이 없으면 다음과 같은 오류가 발생합니다.

추가 정보: PInvoke 함수 ‘DLLCallTest!DLLCallTest.index::GetIPAddress’에 대한 호출 결과 스택이 불안정하게 되었습니다. 관리되는 PInvoke 시그니처와 관리되지 않는 대상 시그니처가 일치하지 않기 때문인 것 같습니다. 호출 규칙 및 PInvoke 시그니처의 매개 변수와 관리되지 않는 대상 시그니처가 일치하는지 확인하십시오.

그러므로 호출표기법에 대한 사항을 명시적으로 지정해 주어야 합니다.

2행의 정의는 좀더 상세하고 3행의 정의는 일반적으로 사용하는 형태입니다.

호출

함수 호출은 동일하게 하면 됩니다. 그런데 인수의 자료형이 byte 배열로 되어 있습니다. 인수의 자료형을 string으로 하면 될 것 같은데 왜 그럴까요? 실제로 string 으로 하고 실행해보면 결과가 정상적으로 반환되지 않습니다.

extern static void GetIPAddress(string ipAddress, string pcName);

string ipAddress = String.Empty;
string pcName = String.Empty;

GetIPAddress(ipAddress, pcName); //결과가 반환되지 않음	

변수에 값을 전달해 주는 것이니 ref 를 붙여 정의하고 호출해 보면 AccessViolationException이 발생합니다.

extern static void GetIPAddress(ref string ipAddress, ref string pcName);

string ipAddress = String.Empty;
string pcName = String.Empty;

GetIPAddress(ref ipAddress, ref pcName); //오류발생
ref 형식으로 호출하면 오류발생

ref 형식으로 호출하면 오류발생

string 형으로 정의되면 DLL 쪽 함수에서 string 변수의 값을 변경할 수 없어 오류가 발생하게 됩니다. 정상적으로 값을 가져오려면 다음과 같이 byte 배열 형태로 정의해야 합니다. byte 배열로 값을 받고 그것을 string 형태로 변환해서 사용하면 됩니다. GetIPAddress 함수는 전달된 인수에 결과값을 할당합니다. 인수가 아닌 return 값으로 반환이 되는 경우 string으로 정의하여 사용하면 됩니다.

extern static void GetIPAddress(byte[] ipAddress, byte[] pcName);

byte[] ipAddress = new byte[250];
byte[] pcName = new byte[250];

GetIPAddress(ipAddress, pcName);

Response.Write("ip : " + System.Text.Encoding.Default.GetString(ipAddress));
Response.Write("pcName : " + System.Text.Encoding.Default.GetString(pcName));
정상적으로 할당된 값

정상적으로 할당된 값

이제까지 C# 에서 DLL 함수 호출 하는 방법을 알아보았습니다.