-
.NET 프로그램에서 확장(Extension) DLL 불러오기Programming/C# 2014. 3. 26. 15:03
2008/09/03 09:45
http://kfship.blog.me/100054511963
이미 .NET Framework 3.5 Beta 버전이 출시되었고, 내년에 Visual Studio 2008이 나오는 시점에서 어떻게 생각하면 구식일 수 있는 확장(Extension DLL)을 불러오는 방법에 대한 글을 쓰는게 시기상 부적절한 일이 될 수도 있겠다라는 생각이 들지만, 사실 우리나라에서 .NET Framework 를 이용하여 대단위의 프로젝트가 진행된 역사가 길지 않기 때문에 아직도 기존의 레거시 시스템과 .NET 프로그램을 연동해서 프로젝트를 진행해야 하는 일이 현장에서는 적지않게 일어날 것이라고 생각한다. 그래서 이번 글에서는 간단히 .NET 프로그램에서 unmanaged 코드로 작성된 MFC 프로그램을 연동하는 방법에 대해서 설명해 보려고 한다.
우선 .NET Framework 로 작성된 Managed Code에서 C++로 작성된 Unmanaged 코드를 불러올 수 있는 방법은 크게 3가지로 구분된다. 첫째는 Unmanaged Code를 Visual Studio가 제공해주는 기능을 통해 Managed Code로 변환하는 것이다. 하지만 이 방법은 변환 이후에도 개발자가 상당히 많은 부분을 수정해야 하므로 추천하고 싶은 방법은 아니다. (사실 실제로 해본적은 없고, 관련된 글을 읽은 적이 있었다. 혹시 의심이 간다면 한번 시도해보기 바란다) 두번째 방법은 기존의 DLL을 COM 으로 변환하는 것이다. 하지만 COM에 관한 전반적인 기술이 없다면 이 방법 또한 쉬운 방법은 아니다. COM 또한 .NET의 출현으로 인해 서서히 잊혀져 가는 기술이기 때문에 새롭게 공부한다는게 쉽지는 않을것 같다. 공부해야 하는 양도 방대하다. 마지막 세번째 방법이 여기서 소개하려고 하는 일반(Regular) DLL과 Managed Code를 연결하는 방법이다.
전체적인 프로그램의 개요는 아래와 같다------------------ --------------- ------------------
; Managed Code ; <---> ; Regular DLL ; <---> ; Extension DLL ;
------------------ --------------- ------------------
1. Managed Code <---> Regular DLL
첫번째로는 Managed Code와 Regular DLL을 연결하는 것이다. 여기서 바로 Managed Code와 확장 DLL을 연결하지 않는 이유는 Managed Code에서 바로 확장 DLL을 인식하지 못하기 때문이다. 하지만 대부분 기존에 작성되어 있는 MFC 프로그램의 DLL 들은 확장 DLL로 작성되어 있기 때문에 Managed Code 와 확장 DLL을 중간에 연결해 줄 수 있는 Regular DLL을 중간에 두는 것이다. 이 때 Managed Code에서 Regular DLL을 호출 할 때는 Widows API와 같이 함수 형태로만 호출이 가능하다. 우선 Regular DLL 쪽에서는 Managed Code에서 부를 함수를
extern "C" __declspec(dllexport) int FuncName(int pN )
{
...
...
}
위와 같은 모양으로 선언해서 DLL 외부로 노출시켜주어야 한다. 이때 Managed Code에서 위에서 노출한 함수를 사용하는 방법은
[DllImport("DllName.Dll")]
public static extern void FuncName(int pN);
위와같이 소스의 상단부에 입력한 이후에 FuncName() 함수를 자유롭게 사용하면 된다.
2. Regular DLL <---> Extension DLL
다음으로 해주어야 할 작업이 Regular DLL과 Extension DLL을 연결해주는 작업이다. 만약 Extension DLL안에서 그리드나 차트와같은 컨트롤을 Main view 페이지에 출력해주는 기능을 갖고 있었다면, 위와 같은 컨트롤을 Managed Code의 WinForm에 출력할 수 없기 때문에 Regular DLL 안에서 Dialog를 하나 출력해준 이후 Dialog 안에서 Extention DLL을 호출해주도록 한다. 여기서 한가지 조심해야 할 것이 있는데, 현재 Managed Code 에서 Regular DLL, Regular DLL 에서 Extension DLL로 프로그램의 제어권이 계속해서 넘어가기 때문에 Regular DLL 또는 Extension DLL 안에서 리소스를 부를 때 자신이 속해있는 메모리 영역이 아닌 다른 영역의 메모리에서 리소스를 참조하려는 에러가 발생할 수 있다. 이때른 런타임 에러가 발생하고 프로그램이 중단되게 된다. 이러한 문제를 해결하기 위해서는 DLL 안에서 리소스를 부를 때 자신이 속해있는 메모리 영역에서 리소스를 참조해오라는 명령어를 입력해주어야 한다. 사용방법은 아래와 같다.
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CMyDialog _myDlg;
_myDlg.DoModal();
우선 위에서 보는것과 같이 Regular DLL 안에서 Dialog를 띄워주기 전 위와 같은 명령어를 사용하여아지 리소스를 올바른 주소에서 참조해온다. 하지만 이 명령어를 써주어야 할 곳이 한곳 더 있다. 바로 Extension DLL을 호출하기 직전이다. Extension DLL 안에서 리소스를 사용하기 전에 위의 명령어를 사용하면 안될까 하고 생각하시는 분이 계실지 모르겠지만 Extension DLL 안에서는 위의 명령어를 사용할 수 없다. 만약 사용한다면 4개 정도의 빌드 에러를 만나게 될 것이다. (사실 Extension DLL 안에서 AfxGetResourceHandle(), AfxSetResourceHandle()과 같은 함수를 이용해서 메모리를 올바르게 참조할 수 있도록 할 수 있지만 스스로 해본 결과 Extension DLL의 함수를 호출하기 전에 Regular DLL 안에서 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 를 호출해주는 것이 가장 속편한다.)
Regular DLL 에서 Extension DLL을 호출하는 방법은 2가지 방법이 있다.
한가지는 명시적인 방법이며 다른 하나는 묵시적인 방법이다. 명시적인 방법으로 연결할 때는 DLL만 필요하며 묵시적인 방법으로 연결할 때는 DLL과 더불어 LIB 파일이 필요하다. 자세한 설명은 인터넷에 찾아보면 자세하게 나온다.
3. Extension DLL 안에서 발생하는 윈도우 Event 처리
보통 하나의 Dialog 안에서 발생하는 윈도우 Event는 PreTranslateMessage(MSG* pMsg) 함수안에서 처리하도록 작업이 되어있다. 예를 들면
if(pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)
{
...
}
위와 같이 처리해주는 것이 일반적이다. 하지만 현재와 같이 .NET을 기반으로 하는 프로그램에서 Regular DLL을 호출하고 Regular DLL에서 호출한 Modaless Dialog위에 다시 Extension DLL을 호출한 상태라면 윈도우 Event가 Extention DLL 안에 선언되어 있는 PreTranslateMessage(MSG* pMsg) 함수 안으로 들어오지 않는다. 어떠한 윈도우 이벤트가 발생하였을 때 윈도우가 먼저 이것을 감지하여 그 Event를 관련된 프로그램의 메시지 큐에 보내며, 각각의 이벤트 큐에서 Event를 처리하게 되는데 위와 같은 구조에서는 이벤트 큐를 통해서 이벤트가 전달되는 곳이 Extension DLL이 아니라 최상위에서 DLL을 호출한 .NET 프로그램이다. 따라서 Extension DLL 안에 override 되어 있는 PreTranslateMessage(MSG p*MSG) 함수는 사용할 수 없다. (만약 메인 프로그램이 .NET 기반의 프로그램이 아니라 MFC 프로그램이었다면 가능한 방법이 있겠지만, 메인 프로그램이 .NET 기반 프로그램이라면, 현재까지 알고있는 바로는 Extension DLL의 PreTranslateMessage(MSG* pMsg) 함수를 실행시킬 방법이 없는것으로 알고 있다. 물론 .NET 에서도 PreTranslateMessage와 같은 역할을 하는 메소들을 재정의해서 Extension DLL에서 발생하는 메시지를 받을 수 있지만 wParam 값 이라던가 lParam 값이 정확히 전달되지 않아 어떤 Event가 발생하였는지 구분하기 힘들다) 이 때 사용할 수 있는 방법중 하나가 메시지 훅 이라는 방법이다. 위에서 설명한 것과 같이 윈도우 Event 발생 시 윈도우가 Event를 받아 일련의 과정을 거치기 전 이벤트를 가로채서 개발자가 필요한 작업을 먼저 해주는 것이다. (물론 다른 방법도 있겠지만 .NET Assembly가 메인 프로그램일 경우 다른 방법을 찾는것이 힘들어 보인다. 누가 알고 있다면 꼭 알려주셨으면 하는 마음이다..) 이벤트 훅에 대해서는 아래 사이트를 참조하기 바란다.
http://winapi.co.kr/win32lec/lec2.htm
위의 사이트에 들어가서 훅 관련된 기사를 본다면 어렵지 않게 Event Hooking에 대해 알 수 있을 것이다출처: http://kfship.blog.me/1000545119632008/09/03 09:46
http://kfship.blog.me/100054512014
이미 지난번 글에서 .NET Form에서 MFC DLL을 불러와 사용하는 방법을 소개한적이 있지만 새롭게 알게된 방법이 있어서 다시한번 글을 쓰려고 한다. 아마도 이번에 소개하려는 방법이 지난번에 소개했던 방법보다 더 쉽고 효율적인 방법이 될 것 같다. 물론 개인적인 생각이긴 하지만..
지난번에 소개했던 방법을 간략하게 이야기 하면 .NET Form에서 Regular Dll로 작성한 Dll을 호출하고, Regular Dll에서 Modaless로 띄운 Dialog에 MFC Extension Dll을 올려놓는 방법이었다. 이 방법은 .NET Form과 Regular DLL 그리고 MFC Extension Dll이 같은 메모리 상에 존재하기 때문에 매개변수를 상호 전달하기 쉽다는 장점이 있지만, MFC Extension Dll에서 발생하는 Window Message를 자기 자신이 받는 것이 아니라 .NET Form에서 받기 때문에 이벤트를 처리하는데 있어서 많은 제약이 있었다. (이벤트 후킹 방법을 사용해면 해결 할 수 있지만, 이것 또한 프로그램의 성능 저하와 Message 가 발생한 컨트롤을 알 수 없다는 단점이 발생한다)
이번에 소개하려는 방법은 지난번과 같이 같은 메모리 상에 MFC Extension Dll을 올려놓는 것이 아니라 서로 다른 Process에 Dll을 올려놓는 방법이다.
1. Dialog 형의 MFC(exe) 프로그램을 생성한다
먼저 exe 파일로 실행할 수 있는 Dialog형의 MFC 프로그램을 생성한다. 현재 생성하는 Dialog 위에 MFC의 Extension Dll을 올려놓을 것이다.
2. C# Form에서 위에서 생성된 MFC 프로그램을 실행한다
System.Diagnostics.Process _process = System.Diagnostics.Process.Start("Proxy.exe");
위와 같이 새로운 Process에 프로그램을 실행한다. 지난번 글에서 Dll 을 실행시켰던것과는 다르게 새로운 Process에 프로그램을 띄우는 이유는 1번 과정에서 생성된 MFC Dialog가 실질적인 Main Form의 역할을 하게 하기 위해서이다. MFC Dialog를 새로운 Process에 띄웠기 때문에 그 위에 다시 띄워지게되는 MFC Extension Dll들은 C#이 아닌 MFC Dialog를 최상위 윈도우로 인식하게 된다
3. MFC Dialog 안에서 C# Form의 핸들을 구해 SetParent 설정을 해준다
MFC Extension Dll을 띄울 프로그램을 새로운 프로세스 위에 띄운것 까지는 좋지만 역시 몇가지 문제가 생긴다. 말 그대로 위의 두 프로그램은 완전 별개의 프로그램이기 때문에 두 창이 동시에 움직이지도 않을 뿐더러 각각 따로 따로 행동하게 된다. 때문에 MFC Dialog 안에서 C# Form을 Parent로 지정하게 되면 MFC Dialog는 C# Form 안으로 들어가게 되며 마치 두 프로그램이 같은 프로그램인것처럼 행동하게 된다.
//.NET 폼의 핸들을 가져온다
g_hMain = ::FindWindow(NULL, "MainForm");
if(g_hMain != NULL)
{
//.NET 폼을 Parent로 설정
::SetParent(GetSafeHwnd(), g_hMain);
//현재 MFC폼의 Handle을 .NET 폼으로 보낸다
::SendMessage(g_hMain, WM_HANDLE, (WPARAM)GetSafeHwnd(), 0);
}
한가지 설명이 안된것이 있는데 SetParent로 .NET Form을 Parent로 설정해준 이후에 .NET Form에도 현재 Dialog의 핸들을 알려주기위해 현재 Dialog의 핸들값을 SendMessage로 .NET Form에 전송한다. WM_HANDLE 값은 사용자가 알맞게 정의한 사용자 정의 Message이다.
TIP. 여기서 MFC Dialog의 Style에서 Title Bar를 표시해주지 않으면 MFC Dialog는 움직이거나 크기 조정을 할 수 없게 된다. 이 상태에서 .NET Form의 Size가 변경될 때 마다 Message를 호출해서 MFC Dialog의 Size를 알맞게 변경해준다면 두개의 프로그램은 완벽히 같은 프로그램으로 보이게 된다.
4. MFC Dialog에서 전송된 Dialog의 핸들값 받아서 전역변수로 선언하기
MFC 프로그램에서 SendMessage로 전송된 Message를 받기 위해서는 .NET의 WndProc 함수를 재정의 해야한다. 또한 계속해서 MFC Dialog와 통신하기 위해서는 Dialog의 핸들값을 전역번수로 선언해 놓는다
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HANDLE)
{
ProxyHandle = new IntPtr(m.WParam.ToInt32());
}base.WndProc(ref m);
}
위에서 WM_HANDLE 값 역시 MFC Dialog에서 정의한 것과 같은 값으로 정의해 놓는다
private const int WM_HANDLE = 0X500; 이 같은 방식으로 저장하면 된다.
위에서 ProxyHandle은 전역변수로 선언되어 있는 Dialog 핸들변수이다 타입은 IntPtr 형이다.
5. .NET Form에서 MFC Dialog로 Messaage 전송하기
MFC Dialog에서 .NET Form으로 메시지를 보내는 방식은 살펴봤기 때문에 .NET에서 MFC Dialog로 메시지를 전송하는 방식만 알게된다면, 우리는 .NET Form과 MFC Dialog 두 윈도우가 서로 호출이 가능하도록 프로그래밍 할 수 있다. .NET에서 MFC Dialog로 Message를 전송하기 위해서는 MFC Dialog에서와 마찬가지로 SendMessage를 이용해야 한다. 하지만 .NET Framework에는 Sendmessage를 전송해주지 않기 때문에 어쩔 수 없이 Window Api를 불러와 이용해야 한다. 방법은 간단하다.
우선 이와 같은 인터페이스를 선언해준다. using System.Runtime.InteropServices; 그리고 나서
[DllImport("User32.dll")]
public extern static long SendMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
DllImport를 이용해 Window Api를 부르면 된다.
이후에는 SendMessage를 클래스 안에서 자유롭게 사용하면 된다. 직접 사용하는 방법을 살펴보면 아래와 같다.
SendMessage(ProxyHandle, WM_FORMRESIZE, new IntPtr(this.Width), new IntPtr(this.Height));
위의 명령을 살펴보면 먼저번에 받아온 MFC Dialog의 핸들로 개발자가 정의한 WM_HANDLE 이라는 Message를 전송한다. 이 때 WPARAM 값은 현재 Form의 Width 값, LPARAM 값은 현재 Form의 Height값으로 설정한다.
6. MFC Dialog에서 .NET Form으로 부터 온 Message 받기
.NET Form으로 부터 전송된 Message는 WindowProc 함수를 재정의해서 받는다.
LRESULT CKOLAS3_PROXYDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if(message == WM_FORMSIZECHANGE)
{
DialogResize((INT)wParam, (INT)lParam,);
}
return CDialog::WindowProc(message, wParam, lParam);
}
위에서 DialogResize 함수는 개발자가 임의로 정의한 함수이다. message와 필요하다면 wParam, lParam을 이용해 Message의 종류를 구분해 각각의 Message에 맞는 처리를 해주면 된다.
7. WPARAM, LPARAM값 이외의 형태 전송하기
위의 방식과 같이 .NET Form과 MFC Dialog가 통신한다면 두 윈도우는 숫자 형태의 자료를 최대 2개까지밖에 전송하지 못하는 한계를 갖게 될 것이다. 그렇다면 그 밖에 문자열과 같은 자료를 어떻게 전송할 수 있을까.. 몇가지 해결방안이 있겠지만 여기서 소개하고 싶은 방법은 WM_COPYDATA 를 이용하는 방법이다. 이에 대한 방법은 직접 기록하지 않고 관련 페이지를 링크하도록 하겠다. CODEPROJECT 사이트에서 WM_COPYDATA로 검색해보면 쉽게 위의 자료를 찾을 수 있을 것이다.
http://www.codeproject.com/csharp/wm_copydata_use.asp
일단 위와 같이 두 윈도우간에 통신이 가능하다면, MFC Dialog 위에 Extension Dll을 올려놓기만 하면 된다. Dialog와 Dll은 둘 다 모두 MFC 기반이기 때문에 통신하는데 큰 제약이 없다.[출처] .NET Form에서 MFC DLL 불러오기 두번째|작성자 kfship
'Programming > C#' 카테고리의 다른 글
C# 간단정리 (0) 2014.03.26 .NET Form에서 MFC DLL 불러오기 두번째 (0) 2014.03.26 대화창 자동 스크롤 (0) 2013.12.12 String Builder (0) 2013.12.12 명명 규칙 (C#) (0) 2013.11.13