카테고리 보관물: 프로그래밍

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 함수 호출 하는 방법을 알아보았습니다.

C# POP3, IMAP 메일 읽어오기

개요

여러가지 이유로 특정 계정의 메일을 읽어와야 할 경우가 있습니다. 필자의 경우 특정한 형식의 메일로 자료를 받아오는 처리를 하게되었습니다. 메일서버와 통신하는 프로토콜은 POP3, IMAP 이 있는데 모두 알아보도록 하겠습니다.

POP3

프로토콜 자체에 대한 설명은 자료를 찾아보면 많이 있습니다. 이번 포스트에서는 서버에 접속해서 목록과 내용을 가져오는 부분을 중점적으로 살펴 보겠습니다. 메일을 읽어오는 방식이 여러가지 방법이 있지만 잘 만들어진 라이브러리를 이용하는 것이 효율적입니다. 여러가지 라이브러리가 있는데 OpenPop.NET 를 사용하겠습니다.

OpenPop.NET은 오픈소스 POP3 client 라이브러리 입니다. 설치는 패키지 관리자 콘솔에서 다음과 같이 입력하면 됩니다.

Install-Package OpenPop.NET

메일 목록을 가져오는 코드를 보겠습니다.

using (Pop3Client client = new Pop3Client())
{
	client.Connect("yourserver", 110, false); //서버접속

	client.Authenticate("youraccount", "yourpassword", AuthenticationMethod.UsernameAndPassword); //서버인증

	var messageCount = client.GetMessageCount(); //받은메일의 메시지 개수

	List<Message> allMessages = new List<Message>(messageCount);

	for (var i = 1; i messageCount; i++)
	{
		var message = client.GetMessage(i);
		var fromAddress = message.Headers.From.Address;
		var subject = message.Headers.Subject;
		var messageBody = String.Empty;

		var plainText = message.FindFirstPlainTextVersion();

		if (plainText == null)
		{
			var html = message.FindFirstHtmlVersion();
			messageBody = html.GetBodyAsText();
		}
		else
		{
			messageBody = plainText.GetBodyAsText();
		}

		var attachFile = message.FindAllAttachments();

		Response.Write("fromAddress : " + fromAddress + "<br />");
		Response.Write("subject : " + subject + "<br />");
		Response.Write("messageBody : " + messageBody + "<br />");
		Response.Write("attachFileCount : " + attachFile.Count + "<br />");
		Response.Write("MessageId : " + message.Headers.MessageId + "<br />");

		foreach (var currentAttachFile in attachFile)
		{
			Response.Write("currentAttachFile : " + currentAttachFile.FileName + "<br />");
			var attachFileInfo = new FileInfo(@"c:\yourpath\" + currentAttachFile.FileName);
			currentAttachFile.Save(attachFileInfo);
		}

		Response.Write("<br /><br /><br />");
	}

	Response.Write("messageCount : " + messageCount);
} 

11행과 같이 시작 인덱스가 1부터 시작한다는 것을 기억해야 합니다.

메일의 내용을 가져올 때 내용이 단순 텍스트인지 html 형태로 되어 있는지에 따로 조금 다릅니다. 일단 18행과 같이 단순 텍스트를 가져 옵니다. 텍스트의 정보가 null 이면 html 형식의 내용으로 간주해서 내용을 가져옵니다. 단순 텍스트 정보이면 그대로 내용을 가져오면 됩니다.

38행~43행은 첨부된 파일의 개수만큼 반복하면서 파일을 저장하는 부분입니다. 필요에 따라 응용하시면 됩니다.

IMAP

모든 서버가 IMAP 프로토콜을 지원하지는 않습니다. 구글메일이 IMAP 프로토콜을 지원하므로 메일을 가져오는 부분을 살펴보겠습니다. 여기에서도 ImapX 라이브러리를 사용하도록 하겠습니다. .NET 용 IMAP 라이브러리 입니다. 패키지 관리자 콘솔에서 다음과 같이 입력하면 설치됩니다.

PM> Install-Package ImapX

메일 제목을 출력하는 코드를 보겠습니다.

Imap4Client imap = new Imap4Client();

var mailServer = "yourmailserver";
var mailAddress = "yourmailaddress";
var mailPassword = "yourpassword";

try
{
	imap.ConnectSsl(mailServer);

	imap.Login(mailAddress, mailPassword);

	Mailbox inbox = imap.SelectMailbox("inbox");

	MessageCollection messages = inbox.SearchParse("OLD UNSEEN");

	if (messages.Count > 0)
	{
		for (int n = 0; n < messages.Count; n++)
		{
			Response.Write(messages[n].Subject + "");
		}
	}
}
catch (Imap4Exception iex)
{
	Response.Write("Imap4 Error: " + iex.Message + "<br />");
}
catch (Exception ex)
{
	Response.Write("Exception: " + ex.Message + "<br />");
}
finally
{
	if (imap.IsConnected)
	{
		imap.Disconnect();
	}
}

15행에 보면 문자로 된 OLD UNSEEN 부분이 있습니다. 검색 명령어인데 전체 명령어는 다음의 주소에서 확인하실 수 있습니다. 6.4.4. SEARCH Command 부분을 보시면 됩니다.

https://tools.ietf.org/html/rfc3501

여러가지 조합이 가능한데 메일을 가져오는 목적에 따라 변경하시면 됩니다.

메시지를 가져온 후 속성을 살펴보면 많은 항목이 있음을 알 수 있습니다. 개발 목적에 따라 적절하게 사용하시면 됩니다.

이상 C# 과 라이브러리를 이용한 POP3, IMAP 메일 읽어오는 방법을 살펴봤습니다.