VB.NET BASIC
C DLL을 만드는 방법
  1. DLL이란 무엇인가?
    DLL (Dynamic Link Libraries)는 윈도우의 주요한 특징이다. DLL은 실행 프로그램에서 콜할 수 있는 함수들을 포함한다. 다시 말하면 DLL은 프로그램 에서 동적으로 링크 가능한 함수들의 라이브러리이다
    링크는 동적 링크와 정적 링크로 나뉜다. 정적 링크는 변동될 수없다. 프로그램에서 사용되는 라이브러리 함수의 어드레스 정보는 실행 파일을 만드는 시점에서 결정되며 실행 중 변경되지 않는다. 
    동적 링크는 필요한 때에 만들어 진다. 실행 파일에 없는 함수를 콜하면, 윈도우OS는 프로그램에서 사용할 수 있도록 DLL을 로드한다. 이때 윈도우OS는 각 함수의 어드레스를 얻어서 프로그램과 동적으로 링크한다. 

    1.1 DLL을 사용하는 이유

    DLL을 사용하는 4가지 이유가 있다:
    1. C Run-Time 함수를 이용하기 위해:
      C Run-Time 라이브러리는 비주얼 베이직에서는 DLL이외에는 이용할 수없는 많은 유용한 함수들을 갖는다.
    2. 콜백 루틴을 포함하는 윈도우API (Application Programming Interface) 
      함수를 호출하기위해 일부 윈도우 API 함수는 콜백 함수를 필요로 한다. 콜백함수는 API 콜을 실행할 때 윈도우에서 실행할 함수이다. 이런 종류의 함수로는 EnumTaskWindows 함수가 있는데, 특정 타스크에서 소유되는 모든 윈도우 핸들을 준다.
    3. 속도 개선을 위해
      C는 native machine code와 유사한 수준의 완전히 컴파일된 언어이다. 즉 C로 잘 만들어진 프로그램의 실행 속도가 빠름을 의미한다.
    4. 사용시 로드하기 위해 DLL의 코드와 데이터는 필요한 경우에만 로드된다. 또한 DLL은 전체가 로드되지 않고 필요한 부분만 로드될 수도 있다. 이것은 로드되는 시간과 메모리를 줄일 수있다.
    1.2 DLL의 핵심

    모든 DLL은 실행 프로그램에서 콜 할 수있도록 익스포트 함수에 추가로 반드시 LibMain 함수와 윈도우 종료 프로시저(WEP)를 포함한다. 

    LibMain: 
    DLL은 반드시 LibMain 함수를 포함한다. LibMain 함수는 DLL 초기화 위해 시스템에서 콜된다. LibMain은 DLL 로드가 필요한 첫번째 프로그램에 의해 한번만 로드된다. 다음은 LibMain의 매개변수이다:

    - HANDLE : DLL 인스턴스 핸들.
    - WORD : 라이브러리의 데이터 세그먼트.
    - WORD : 힙 크기.
    - LPSTR : 커맨드 라인 매개변수.
    - WEP: WEP (Windows Exit Procedure)은 DLL이 언로드되기 전에 클린업을 수행한다. 윈도우 3.1이전의 윈도우 OS에서는 모든 DLL에서 WEP함수가 반드시 필요했으나, 3.1이후에는 선택적이다. WEP은 모듈 정의 파일(.DEF)에 반드시 정의되어야 한다:
    EXPORTS
       WEP
    					
    - 익스포트 함수: DLL에서 콜되기를 원하는 함수들이다. _export로 표시되며, DEF 파일에 리스트된 모든 함수들을 콜할 수있다. 

    1.3 DLL 메모리 관리 문제

    Large 메모리 모델
    C 는 프로그램 힙 공간에 모든 Static과 Global(함수의 외부에 정의된) 변수에 저장하고, 그외의 변수는 스택에 저장한다. Small 과 Medium 모델에서는 모든 포인터는 디폴트로 Near이다. 이것은 동일 데이터 세그먼트(DS), 혹은 스택 세그먼트(SS)의 16비트 오프셋으로 데이터에 접근한다는 의미이다. 
    불행히도 컴파일러에서 DS나 SS로 부터의 오프셋을 알 수 있는 방법이 없다. 
    대부분의 프로그램은 DS와 SS가 동일 세그먼트를 포인트 하므로 문제가 되지 않으나, DLL은 좀 다른 경우이다. DLL은 자신의 데이터 세그먼트를 따로 관라하고 스택만을 콜하는 프로그램과 공유한다. 이것은 DS와 SS가 동일 위치를 포인트하지 않는다는 의미이다. 가장 쉽게 DLL을 large 메모리 모델로 만들면 모든 변수는 32-비트로 참조된다. 

    왜 메모리를 동적으로 할당하는가?
    동적으로 메모리 할당은 윈도우에서는 일반적이다. 큰 데이터 배열을 64K로 제한된 스택에 선언하거나 디스크 공간과 윈도우 메모리를 낭비하는 데이터 세그먼트에 선언하는 것 보다는 필요한 때 메모리를 할당하고 필요하지 않을 때 종료하는 것이 더 낫다. 

    메모리 할당
    윈도우에서 2가지 종류의 메모리, Local 과 Global의 메모리를 동적으로 할당할 수있다. Local메모리의 한계는 64K이고 DLL의 경우 DLL을 콜하는 프로그램과 공유한다. Global 메모리는 윈도우 로드후 이용 가능한 모든 메모리 공간이다. Local 메모리는 LocalAlloc, LocalLock, LocalUnlock, LocalFree 함수로 사용되며 다음은 예이다:
       char* pszBuffer;
       ....
       pszBuffer = (char *) LocalAlloc (LPTR, 20);
       ...
       LocalFree (pszBuffer);
    					
    Local 메모리 할당이 Global 메모리 할당보다 빠르다. 그러나 Local 힙은 64K로 제한되며 DLL을 콜하는 모든 프로그램과 공유해야 한다. 즉 Local 메모리는 가능한한 작게 하는 것이 좋다. Global 메모리는 GlobalAlloc, GlobalLock, GlobalUnlock, GlobalFree 함수로 사용되며 다음은 예이다:
       HGLOBAL hglb;
       char* pszBuffer;
    
       hglb = GlobalAlloc (GHND, 2048);
          // GHND moveable 하며 0으로 초기화되는 메모리를 할당한다.
          // 2048은 할당되는 메모리의 크기
       pszBuffer = GlobalLock (hglb);
       ...
       GlobalUnlock (hglb);
       GlobalFree (hglb);
    					
    GlobalAlloc 함수는 4K배수로 할당한다. DLL에서 다른 프로그램과 공유하는 메모리를 할당하는 경우에는 GMEM_SHARED 플래그를 사용한다. 만약 DDE를 통해 메모리를 공유하는 경우에는 GMEM_DDESHARE 플래그를 이용 할당한다. Static 변수에 데이터 저장시 주의한다. 
    Global이나 Static변수를 이용하여 DLL에서 데이터를 저장하는 경우 DLL의 다음 함수 콜시 값이 바뀌어져 있을 수있다. 이렇게 저장된 데이터는 DLL을 사용하는 모든 프로그램에서 사용하게 된다. 얼마나 많은 프로그램에서 DLL을 사용하건 DLL의 인스턴스는 하나이다. 
    이 문제를 피하려면 필요한 구조체를 DLL에 패스하고 다시 DLL에서 리턴 받는 방법을 사용한다. 

    파일 핸들
    어플리케이션과 DLL간의 파일 핸들은 공유할 수없다. 각 어플리케이션은 자신의 파일-핸들 테이블을 갖는다. 두 어플리케이션이 하나의 DLL을 사용하는 동일 파일을 사용하려면 각각 따로 그 파일을 오픈한다. 

    1.4 VC++를 이용 DLL을 만들기

    다음은 비주얼 C++를 이용하여 DLL을 빌드하는 방법이다:
    1. 비주얼 C++ 시작한다.
    2. 다음의 옵션으로 Project의 New를 선택한다:
      - Project Type을 "Windows dynamic-link library (.DLL)"로 지정
      - "Use Microsoft Foundation Classes" 체크 박스를 지운다.
      Options메뉴의 Project를 선택하여 지정/보기를 할 수있다.
    3. 아래의 예제 C와 DEF를 Project에 추가한다.
    4. Project메뉴의 Build <DLL명>.DLL 을 선택한다.
    1.5 C DLL의 예제

    DLL은 VB에서 콜될 GetDiskInfo 함수를 포함한다. 이 함수는 이용 가능한 디스크 공간과 현재의 드라이브 명, 볼륨 명을 리턴한다.
    C 코드 예제, DISKINFO.C:
    
    #include <windows.h>
    #include <dos.h>
    
    int CALLBACK LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, 
    LPSTR lpszCmdLine)
    // 다음은 윈도우 3.1에서만 필요한 UnlockData()이다
    {
       if (wHeapSize > 0)
          UnlockData (0);  //라이브러리의 데이터 세그먼트를 Unlocks
       return 1;
    }
    
    void __export CALLBACK GetDiskInfo (char *cDrive, char *szVolumeName, 
    unsigned long *ulFreeSpace)
    {
       unsigned drive;
       struct _diskfree_t driveinfo;
       struct _find_t c_file;
    
       _dos_getdrive (&drive);
       _dos_getdiskfree( drive, &driveinfo );
    
       if (!_dos_findfirst( "*.*", _A_VOLID, &c_file )) wsprintf( szVolumeName,
    "%s", c_file.name);
       else  wsprintf ( szVolumeName, "NO LABEL");
    
       *cDrive = drive + 'A' -1;
    
       *ulFreeSpace = (unsigned long) driveinfo.avail_clusters * (unsigned     
    long) driveinfo.sectors_per_cluster * (unsigned long) driveinfo.bytes_per_sector;
    }
    
    DISKINFO.DEF 파일 예제 
       LIBRARY  diskinfo
       DESCRIPTION 'GetDiskInfo 는 VB에서 콜된다.
       EXETYPE WINDOWS 3.1
       CODE PRELOAD MOVEABLE DISCARDABLE
       DATA PRELOAD MOVEABLE SINGLE
       HEAPSIZE    4096
       EXPORTS
          GetDiskInfo @1
    					
    주: LIBRARY명과.DEF 파일명은 반드시 DLL 파일명과 동일해야 한다. 그렇지 않으면 VB는 "Error in loading DLL"의 오류를 낸다. 

    C DLL 함수를 VB에서 콜하는 방법
  2. VB에서 DLL 콜하기

    VB의 DLL 함수를 포함한 모든 함수는 반드시 Declare문을 사용하여 먼저 선언한다. 함수는 반드시 Form 이나Module의 Declare 섹션에서 선언할 수있다. 만약 Form 에서 DLL 프로시저나 함수를 선언하면 해당 Form에만 Private하므로 Public하려면 반드시 Module에 선언한다. 다음은 Declare문의 예이다:
    Declare Sub getdiskinfo Lib "c:\somepath\diskinfo.dll" (ByVal mydrv As 
    String, ByVal myvol As String, free As Long)
    					
    위의 Declare 문은 DISKINFO.DLL 파일의 사용자-정의 프로시저 GETDISKINFO를 선언한다. 
    함수를 선언한 후에는 VB 함수처럼 콜할 수있게 된다.

    2.1 DLL 매개변수

    DLL은 C로 만들기 때문에 VB에서 직접 지원하지 않는 매개변수를 사용할 수있다. 그 결과 프로그래머는 패스되는 적절한 데이터 타입을 찾아야만한다. 

    by Value 나by Reference에 의한 아규먼트 패스
    기본적으로, VB는 모든 아규먼트를 by reference (by reference로 전달 시, VB는 32-bit far 어드레스를 제공한다.) 그러나 많은 DLL 함수들은 패스되는 아규먼트가 by value로 패스된 다고 기대한다. 이렇게 하기 위해서는 ByVal 키워드를 사용하면 된다. 

    8-에서16-Bit 숫자 매개변수 
    8-에서16-Bit 숫자 매개변수(int, short, unsigned int, unsigned , short, BOOL, WORD)는 as Integer로 선언.

    32-bit 숫자 매개변수
    32-bit 숫자 매개변수(long, unsigned long, DWORD)는 as LONG으로 선언.
    Object 핸들
    모든 윈도우 핸들을 유일한 16-bit 정수이며 by value로 패스되므로 as Integer로 선언.

    문자열
    문자열은 LPSTR 와 LPBYTE 데이터 타입 ( 문자들과 부호없는 문자들의 포인터)이다. 이들 변수는 (ByVal paramname As String)로 선언.

    숫자 값의 포인터 
    숫자 값의 포인터는 ByVal 키워드를 사용하지 않도록 선언.

    구조체
    만약 VB 사용자-정의 타입이 DLL의 구조체와 일치하면, 구조체는 by reference 선언.
    주: 구조체는 by value로 패스되지 않는다.

    배열의 포인터
    배열의 첫 요소를 by reference로 패스한다.

    함수의 포인터
    VB는 콜백함수를 지원하지 않으므로 함수의 포인터를 갖는 DLL함수는 VB에서 이용될 수없다.

    널 포인터
    DLL에서 널 포인터가 패스되어야 하는 경우, (ByVal paramname As Any).로 선언하고 DLL 콜시 paramname 의 값을 &0로 패스한다. 

    2.2 문제 해결

    다음은 문제를 해결하는 방법을 설명한다. 
    DLL 콜 된후 시스템 리소스가 적어진다. 
    만약 DLL이 GDI 오브젝트를 사용하는 경우, 사용후 반드시 해제해야한다. 
    즉 Windows SDK (software development kit) 로 GDI 오브젝트(예로, CreateBrushIndirect)를 생성하면, 반드시 사용 후, DeleteObject로 해제해야한다

    "Bad DLL Calling Convention Error"
    주로 Declare문에서 잘못 ByVal을 넣거나 빼는 경우 발생하거나 잘못된 매개변수가 패스될 때 나타난다.

    Error in loading DLL
    이 에러는DLL 파일의 프로시저를 로드하였는데, 프로시저의 Declare문에서 선언한 DLL을 로드하지 못한 경우 발생한다. DLL의 로드 실패의 이유는 윈도우 API 함수 LoadLibrary 를 이용하여 더 자세한 정보를 알 수있다. 

    일반 보호 오류(GPF)
    GP 오류는 프로그램에서 자신에 속하지 않는 메모리 블록에 쓰기를 하는 경우 발생한다. 다음은 대표적인 2가지 이유이다:
    - 배열의 경계를 넘은 경우. C에서는 배열의 한계를 체크하지 않아 쉽게 다른 메모리에 쓰기를 할 수있다. 
    - 해제한 메모리의 위치 포인터를 사용하는 경우, 가장 좋은 방법은 해제한 메모리는 모두 NULL을 지정하는 방법이다.
    GP 오류는 DLL 함수에 잘못된 변수 타입이 전달되는 경우에도 발생한다. 

    2.3 VB에서 콜하는 프로그램의 예제 
    VB에서 DLL을 콜하는 부분은 2 부분으로 나뉜다. 첫번째는 함수 선언부이고 두번째는 이벤트 코드에서 사용하는 부분이다.
    다음은 Declare 문 예이고, Declare 문은 반드시 모듈의 Declare부나 폼의 General Declarations에서 한다.
       ' 다음은 Declare 부분
       Declare Sub getdiskinfo Lib "c:\dllartic\diskinfo.dll" (
    ByVal mydrive As String, ByVal myvolume As String, free As Long)
    					
    ByVal 을 정확히 하지 않으면 GP 오류가 날수있다. 함수의 선언후 이벤트 코드에서 사용할 수있다. 다음은 Command1 Click 이벤트에서 DLL 함수를 콜하는 경우이다.
    Sub Command1_Click ()
       Dim drive As String * 1
       Dim volume As String * 20
       Dim free As Long
       Call getdiskinfo(drive, volume, free)
       Text1.Text = drive
       Text2.Text = volume
       Text3.Text = Str$(free)
    End Sub