반응형

http://rainiac.com/dev/index.php/dll-%EC%A0%9C%EC%9E%91%EB%B0%A9%EB%B2%95-1-non-mfc-dll/


DLL 제작방법 – 1. Non-MFC DLL

1.1Basis

 

1.1.1Features

  • 내부적으로 C++ 클래스를 사용할 수 있고, C 함수 Wrapper만을 Export 할 수 있다. 따라서 내부적인 C++ Class에 대한 변경은 DLL의 호출에 영향을 주지 않는다.
  • MFC를 사용할 수 없으며, 별도의 MFC Library가 필요없다.
  • DLL을 사용하는 Client는 DLL 호출을 지원하는 어떠한 Language로 작성될 수 있다.
  • AppWizard를 이용하여 자동으로 Project를 생성할 수 있다.

 

1.1.2 Function Export

DLL 내에서 정의된 Function을 export하기 위해서는 “__declspec(dllexport)” 를 사용한다. “__declspec”은 MS만의 C, C++의 확장된 syntax로서, 확장된 storage-class 정보를 정의한다. “dllexport”는 storage-class의 한 속성으로, DLL의 Function, Data, Object를 export할 수 있도록 하여준다. 반대로 DLL내의 Function을 import하기 위해서는 “dllimport” 속성을 사용한다. Win32 환경에서는 Function Export/Import를 위하여 이것을 이용하며, Win32 이전의 Windows 환경에서 사용되던 module-definition file (.DEF)을 사용하지 않는다. 단, .Net 이전의 VB 등의 툴과 호환가능한 DLL을 제작하는 경우, module-definition file을 사용하도록 한다.

 

· export / import

함수 export/import를 위하여 아래와 같이 함수를 선언한다. Coding의 편의를 위하여 export선언을 #define으로 간략화시킨다.

#define DLLImport  __declspec(dllimport)
#define DLLExport  __declspec(dllexport)

DLLExport void somefunc();

· export/import Tips

위 방법으로 export/import 함수를 정의하면, DLL 내에서의 함수 정의와 DLL을 사용하는 Client에서의 함수정의를 다르게 해야 하는 불편이 생긴다. DLL과 Client에서 동일한 Header File을 사용할 수 있도록 하기 위하여 아래와 같이 export/import 함수를 정의한다.

#ifdef DLLTEST_EXPORTS
    #define DLLFunction  __declspec(dllexport)
#elseif 
    #define DLLFunction  __declspec(dllimport)
#endif

DLLFunction void somefunc();

“DLLTEST3_EXPORTS” 은 DLL의 Project Settings에 Preprocessor definitions에 “프로젝트명_EXPORTS”의 형식으로 정의 되어 있다. 따라서 DLL Project에서는 export로, Client Project에서는 import로 동작한다.

 

· Adjusting Naming Convention

C++은 C와 다른 Naming Convention을 사용한다. 따라서 export되는 함수명은 Compile시에 기존의 정의한 이름과 다르게 해석된다. 따라서 Naming Convention에 대한 조정과정이 없으면, export된 함수는 C++ 이외의 Language로 작성되는 프로그램에서는 호출될 수 없다. extern “C”는 함수가 C naming convention을 사용하도록 만들어주며, 이를 통하여 C++로 작성되어 export되는 함수를 다른 Language에서도 사용가능하도록 하여 준다. VC는 기본적으로 프로젝트생성시에 C++을 사용하도록 구성되므로 모든 export함수는 Naming Convention의 조정이 필요하다.

#ifdef _USRDLL
    #define DLLFunction  __declspec(dllexport)
#elseif 
    #define DLLFunction  __declspec(dllimport)
#endif

#ifdef __cplusplus
extern “C” {
#endif

DLLFunction void somefunc();

#ifdef __cplusplus
}
#endif

· Adjusting Calling Convention

이전의 Visual Basic과 같은 C 이외의 다른 언어는 C와 다른 Calling Convention을 사용하므로 다른언어에서 사용될 DLL을 작성하는 경우, 이를 조정해주어야 한다. VC는 기본적으로 “__cdecl” 방식을 사용하며, 다른 언어는 표준방식인 “__stdcall” 방식을 사용한다. 따라서 Project 속성페이지에서 “구성속성 – C/C++” 페이지의 “고급” 항목을 선택하여 “호출 규칙”을 “__stdcall”로 설정한다.

image

 

속성을 사용하지 않고 코드내에서 직접 지정하고 싶다면 아래와 같이 함수 선언과 구현시 함수명 앞에 Calling Convention을 지정해 준다.

 

DLLFunction void __stdcall somefunc();

DLLFunction void __stdcall someFunc()

{

}

 

 

만약 DLL을 .Net 이전의 Visual Basic 과 같은 언어에서 사용하고자 한다면, module-definition file(.Def)을 작성하여 프로젝트에 포함시킨다. VC로 작성한 DLL을 예전의 Visual Basic에서 사용하는 경우, Calling Convention이 다르다는 에러를 자주 볼 수 있는데, 이는 모두 Calling Convention을 조정과정을 거치지 않아서 발생하는 문제이다.

 

Win32DLLSample.def

LIBRARY                   "Win32DLLSample.DLL"

 

EXPORTS

   somefunc   @1

 

1.2 Making DLL

1.2.1 목표

간단한 DLL을 만들기 위하여 아래와 같은 두가지의 기능만을 가지는 DLL을 만들기로 한다.

 

  • int 형의 두 숫자를 parameter로 받아 그 합을 return하는 함수
  • 두개의 string을 받아 연결된 string을 넘겨주고, 그 총 길이를 return하는 함수

 

1.2.2 구현방법

  • 새 프로젝트를 선택한 후, 프로젝트 유형을 아래와 같이 Visual C++ 카테고리의 “Win32 프로젝트”로 선택한다.

image

 

  • 용 프로그램 마법사에서 아래와 같이 “DLL”을 선택 후, 마침을 클릭하여 프로젝트를 생성한다.

image

 

  • export할 함수의 정의를 위하여 Header File을 생성한다. 이는 후에 import 측에서도 공용으로 사용될 것이다. “SimpleDll.h”의 이름으로 Header를 생성하고, 두개의 함수를 위한 함수정의를 만든다.

[Win32DLLSample.h]

#pragma once
 
#ifdef WIN32DLLSAMPLE_EXPORTS
   #define DLLFunction __declspec(dllexport)
#else
   #define DLLFunction __declspec(dllimport)

#endif
 
extern "C" {
 
DLLFunction int __stdcall addint(int n1, int n2);
DLLFunction int __stdcall addchar(char* s1, char* s2, char* added);
 
}

  • 선언한 함수에 대하여 기능을 작성한다. 두 함수는 단지 int형과 char형의 더하기 만을 지원하므로 아래와 같이 함수를 작성한다.

[Win32DLLSample.cpp]

#include "stdafx.h"
#include "stdio.h"
#include "Win32DLLSample.h"
 
DLLFunction int __stdcall addint(int n1, int n2)
{
   return n1 + n2;
}
 
DLLFunction int __stdcall addchar(char* s1, char* s2, char* added)
{
   sprintf(added, "%s%s", s1, s2);
   return strlen(added);
}

  • 여기까지 작성하였다면 컴파일하여 Lib 파일과 DLL 파일을 얻을 수 있다.

 

1.3 Using DLL

앞에서 작성한 코드를 컴파일하여 Win32DLLSample.dll 파일을 얻는다. 이것을 테스트하기 위하여 VC++ 및 VC#에서 DLL내의 함수를 호출하는 기능을 작성한다.

 

1.3.1 Using DLL with Visual C++ (Link Implicitly)

Dialog-Based 프로젝트를 생성하여 아래와 같은 순서로 DLL의 함수를 호출하는 기능을 작성한다.

 

  • 앞에서 작성한 “Win32DLLSample.h”를 프로젝트에 추가한다.
  • 프로젝트 속성페이지에서 “구성속성 – 링커 – 입력”을 선택한 후, “추가종속성” 항목에 “Win32DLLSample.lib”를 추가한다. “Win32DLLSample.lib” 파일은 VC++의 환경설정의 Directories에 존재하는 폴더 또는 Project 폴더에 복사하도록 하거나 “구성속성 – VC++ 디렉터리”를 선택하여 “라이브러리 디렉터리” 항목에 Lib 파일이 존재하는 폴더를 추가한다.

image

 

  • Dialog를 다음과 같이 만들고 각 컨트롤에 멤버변수를 추가한다.

image

  • DLL 함수를 호출할 Button에 대한 Handler를 작성한다.

[Win32DLLSampleTestDlg.cpp]

#include "Win32DLLSample.h"void CWin32DLLSampleTestDlg::OnBnClickedButtonInt()
{
   UpdateData(TRUE);
   m_nIntSum = addint(m_nInt1, m_nInt2);
   UpdateData(FALSE);
}
 
 
void CWin32DLLSampleTestDlg::OnBnClickedButtonChar()
{
   char s1[9];
   char s2[9];
   char sSum[17];
 
   UpdateData(TRUE);
 
   lstrcpy(s1, (LPCTSTR)m_sChar1);
   lstrcpy(s2, (LPCTSTR)m_sChar2);
 
   addchar(s1, s2, sSum);
 
   m_sCharSum = sSum;
   UpdateData(FALSE);
}

위의 방법으로 DLL 함수를 호출하는 프로그램을 작성하여 실행하면 각 함수들이 정확하게 호출되고 있음을 확인할 수 있다.

 

1.3.2 Using DLL with VC++ (Link Explicitly)

Explicit Link를 사용하여 DLL 함수를 호출하는 경우, Header 파일과 Library 파일은 필요하지 않다. 단지 그 함수의 원형만을 알고 있으면 된다. Explicit Link를 사용하기 위하여 아래와 같은 순서로 DLL 함수를 호출하는 기능을 작성한다.

 

  • 1.3.1에서와 같이 Dialog-Based 프로젝트를 생성한 후, 같은 모양으로 Dialog를 만들고 멤버변수를 연결한다.

 

  • DLL 함수호출을 위한 Button의 Handler를 만들고 아래와 같이 코드를 작성한다.

[SimpleDllTest2.Dlg.cpp]

void CSimpleDllTest2Dlg::OnAddint() 
{
   int (*lpfnAddInt)(int, int);
   HINSTANCE hLib = LoadLibrary("Win32DLLSample.dll");
   if(hLib == NULL)
                 return;

   lpfnAddInt = (int(*)(int, int))GetProcAddress(hLib, "addint");
   if(lpfnAddInt == NULL)
   {
                 FreeLibrary(hLib);
                 return;
   }

   UpdateData(TRUE);
   m_nSumInt = lpfnAddInt(m_nInt1, m_nInt2);
   UpdateData(FALSE);

   FreeLibrary(hLib);
}

위와 같이 LoadLibrary를 이용하여 Explicit Link로 DLL 함수를 호출하도록 하면, 별도의 Library와 Header가 필요하지 않으며, 실행 시간에 DLL의 Load와 Unload의 시점을 결정할 수 있는 장점이 있다.

 

1.3.3 Using DLL with C#

C#에서는 DLL의 함수 호출이 매우 간단하다. 단지 어떤 Library의 어떤 함수를 사용할 것인지에 대한 선언만 정확이 명시하면 된다. 아래와 같이 “addint” 와 “addchar” 함수를 선언한다.

C#은 Calling Convention 등을 모두 속성으로 설정 가능하므로, 대부분의 DLL을 그대로 호출할 수 있다.

[DllImport("Win32DLLSample.dll")]
public static extern int addint(int n1, int n2);
[DllImport("Win32DLLSample.dll")]
public static extern int addchar(string s1, string s2, StringBuilder sum);

함수선언 후, 실행을 위한 Handler에서 아래와 같이 호출한다.

private void btnInt_Click(object sender, EventArgs e)
{
   int n1, n2;
   n1 = int.Parse(txtN1.Text);
   n2 = int.Parse(txtN2.Text);

   int sum = addint(n1, n2);
   txtNSum.Text = sum.ToString();
}

private void btnString_Click(object sender, EventArgs e)
{
   var sb = new StringBuilder(100);
   addchar(txtS1.Text, txtS2.Text, sb);
   txtSSum.Text = sb.ToString();
}

위에서 사용된 DllImport Attribute는 몇가지 옵션필드를 가질 수 있다. 이 필드 중 주요한 항목은 아래와 같다. 자세한 내용은 DllImportAttribute Class를 참조한다.

필드

설명

Calling Convention

DLL 내의 Export 함수에 대한 Calling Convention을 지정할 수 있다.

Cdecl, Winapi, StdCall 을 지원하며, 기본값은 StdCall 이다.

CharSet

문자열에 사용할 Character Set을 설정한다.

None(자동), Unicode 값을 가질 수 있다.

Entry Point

DLL 내의 함수가 호출되는 이름을 나타낸다.

이를 이용하면 함수진입점을 지정하여, 선언시 다른 이름으로 별칭을 이용할 수도 있다.

 

아래는 DllImport Attribute를 사용하여 함수에 별칭을 부여한 예이다.

[DllImport(“user32”, CharSet = CharSet.UniCode, EntryPoint = “MessageBoxW”)]
public static extern int MsgBox(int hWnd, String pText, String pCaption, int uType);

Unmanaged Code의 Data Type은 C#(Managed Code)에서 아래와 같이 변환한다.

C (Unmanaged Code)

C# (Managed Code)

HANDLE, void* 또는 일반 pointer

IntPtr

BYTE, unsigned char

Byte

short

Short

WORD, unsigned short

Ushort

int

int

UINT, unsigned int

uint

long

int

BOOL, long

int

DWORD, unsigned long

uint

char

char

LPSTR, char*

string 또는 StringBuilder

LPCSTR, const char*

string 또는 StringBuilder

BSTR

string

float

float

double

double

HRESULT

int

VARIANT

object

 

반응형

+ Recent posts