11111111 11111111 11111111 10001110 (-114)

00000000 00000000 00000000 11111111 (0xFF)

------------------------------------- 20150831 추가



public class T

{
    public static void main(String[] args) throws Exception

    {
        byte[] b = { (byte)0x34, (byte)0x32, (byte)0x31, (byte)0x8E, (byte)0xF0 };
        System.out.println( "i:\t b[i]\t 0xff&b[i]\t toHex(0xff&b[i])" );
        for( int i=0; i<=4; i++ )

        {

            System.out.println(i + ":\t" + b[i] + "\t" + (0xFF&b[i]) + "\t" + Integer.toHexString(0xFF&b[i]));
        }
    }
}
========================================
C:\>java T
i: b[i] 0xff&b[i] toHex(0xff&b[i])
0: 52 52 34
1: 50 50 32
2: 49 49 31
3: -114 142 8e
4: -16 240 f0
========================================

1. byte & int & bit
1 byte = 8bit : -128 ~ 127, -2^(8-1) ~ 2^(8-1)-1 : 2^8개
1 int = 4 byte = 32bit : -2,147,483,648 ~ 2,147,483,647, -2^(32-1) ~ 2^(32-1)-1 : 2^32개

byte: 00000000
int: 00000000 00000000 00000000 00000000

0x34 = 00000000 00000000 00000000 00110100
(byte)0x34 = 00110100

0x32 = 00000000 00000000 00000000 00110010
(byte)0x32 = 00110010
...
0x8E = 00000000 00000000 00000000 10001110
(byte)0x8E = 10001110 (NOTE: sign bit is 1)

0xF0 = 00000000 00000000 00000000 11110000
(byte)0xF0 = 11110000 (NOTE: sign bit is 1)

2. (byte)0xF0 = 11110000 인데, 가장 좌측의 sign bit가 1이므로, 이는 음수를 뜻합니다.
byte형에 적재된 11110000의 "2의보수"는 00010000 입니다.
  11110000
+ 00010000
------------
  1 00000000  (마치, 10진수 3의 "10의보수"는 7 이듯...)

00010000 는 2^4 = 16 이므로, 결국 (byte)0xF0 = 11110000 는 -16으로 나타나게 됩니다.

3. 0xFF & b[i]
ex) 0xFF & (byte)0xF0

0xFF = 00000000 00000000 00000000 11111111 { = 32-bit = Integer }
                                           & 11110000 { = (byte)0xF0 = -16 }
        ------------------------------------
         00000000 00000000 00000000 11110000 = 0xF0 = 240 { = 32-bit = Integer }


NOTE: byte로 타입 캐스팅을 해서 출력하면 이상한 값이 들어가는 것처럼 보이는 이유는 byte도 int의 경우처럼 맨 앞의 비트를 sign bit 으로 사용하기 때문입니다. 8bit로 표현되는 0xFF 이하의 수치(0~255)에 한해서 (byte)형으로 casting하게 되면 하여 집어넣을 경우 & 0xFF 연산을 해 주게되면 32-bit 크기(int)로 unsigned byte의 값을 얻을 수 있습니다. (참고: 자바에는 primitive type 으로 unsigned 형이 없습니다.)

 

=========================================================================================================

 

자바에서 "int"형은 4byte 로 표현됩니다. 1byte는 8bit 이니 int형은 총 32 bit 로 표현됩니다.

예) int형 1의 bit 표현은 결국 다음과 같습니다.
     00000000 00000000 00000000 00000001

32bit를 이용하여 표현하니 총 표현할 수 있는 수치의 개수는 2의 32승, 즉 4,294,967,296 개가 될 것입니다.

그러나, 가장 좌측 bit 는 음수표식을 위해 사용되니(sign bit), 0을 포함하여 31bit로 표현될 수 있는 수의 개수는 2의 (32-1)승개가 됩니다. 2의 31승은 21,47,483,648 이니, 양수로 가장 큰 수는 21,47,483,648-1 입니다. 이 수는 4byte 형에서 다음과 같이 표현됩니다.
01111111 11111111 11111111 11111111 = 21,47,483,647

음수는 "2의보수"로 표현되는데,  예를 들면 int형(4byte,32bit) -1은 "2의보수"로 표현되어,

11111111 11111111 11111111 111111111 로 저장(?) 됩니다.

이는 마치 10진법 3의 10의 보수는 7, 즉
  3
+ 7
----
 10

이듯이,

  00000000 00000000 00000000 000000001
+ 11111111 11111111 11111111 111111111
--------------------------------------
1 00000000 00000000 00000000 000000000

이므로, 00000000 00000000 00000000 000000001 의 int형 "2의 보수"는
           11111111 11111111 11111111 111111111 인 것이지요.

"2의보수"를 찾는 알고리즘은 통상 NOT연산 후 +1을 취하면 됩니다. 즉,
|-1| 은 1입니다.  1은 이진수로   00000000 00000000 00000000 000000001 입니다. 
   이것에 대해 먼저 NOT연산후   11111111 11111111 11111111 111111110,
                         +1 을 취하면   11111111 11111111 11111111 111111111 입니다.

|-21,47,483,647| 은 01111111 11111111 11111111 11111111 인데,
NOT연산을 취하면  10000000 00000000 00000000 00000000 이고,
+1 을 취하면          10000000 00000000 00000000 00000001 입니다.

|-21,47,483,648| 은 10000000 00000000 00000000 00000000 인데,
NOT연산을 취하면  01111111 11111111 11111111 11111111 이고,
+1 을 취하면          10000000 00000000 00000000 00000000 입니다.

이 처럼 가장 좌측 bit 가 1 인 상태에서 2의 보수로 표현될 수 있는 가장 작은 수는 -21,47,483,648 이 됩니다.

long 형은 8 byte, 64 bit로 표현된다는 것 외엔 동일합니다.
결국, long형의 가장 큰 수는 2의 (64-1)승 -1 인, 9,223,372,036,854,775,807 까지
표현 됩니다. 음수로는 -{2의 (64-1)승} 인 -9,223,372,036,854,775,808 까지 표현되겠지요.

추가로 아래 글들을 참고하세요.

-128>>>1 = 2147483584 ??? 
http://www.javaservice.net/~java/bbs/read.cgi?m=qna&b=javatip&c=r_p&n=998318237

Re: 1>>> 32 가 1인 이유는 ???? 
http://www.javaservice.net/~java/bbs/read.cgi?m=qna&b=QandA&c=r_p&n=994003831

[자바의 숫자표현] 어디까지 가능할까 !!! 
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=920434490
 

 


[출처] 이원영, 192 Re: 자바의 자료형
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=1018155018

'language > JAVA' 카테고리의 다른 글

Decompiler jad  (0) 2010.07.22
JAVA Native Method (JNI)  (1) 2009.07.01
JNI (Java Native Interface) 란 ?  (0) 2009.06.30
블로그 이미지

란마12

,

간략한 용어 정리

다음은 Visual Basic 개체와 그 기능을 검토하는 과정에서 만나게 되는 용어들입니다. Visual Basic을 사용하기 전에 다른 프로그래밍 언어나 ActiveX(이전의 OLE) 용어로 작업을 했다면 이 부분을 변환하는데 도움이 될 것입니다.

개체에 익숙하지 않으면 모든 것이 다소 당황스러울 것입니다. 개체가 어떻게 이루어졌는지를 보여주는 그림 한 장을 보는 것을 시작으로 부딪치게 될 용어를 간단히 살펴보게 될 것입니다. 이 장의 나머지 부분에서 개체에 대해 좀 더 알게 되면 이 항목에서 제공하는 정보를 전체적으로 이해할 수 있을 것입니다.

시작하기

개체는 캡슐화되어 있습니다. 말하자면 개체는 코드와 데이터를 모두 포함하기 때문에 전통적인 코드 작성 방법보다 쉽게 관리할 수 있습니다.

Visual Basic 개체에는 속성, 메서드, 이벤트가 있습니다. 속성은 개체를 설명하는 데이터입니다. 메서드는 사용자가 원할 때 개체가 실행할 프로시저입니다. 이벤트는 개체가 수행하는 작업으로, 이벤트가 발생했을 때 실행되는 코드를 기록할 수 있습니다.

Visual Basic 개체는 클래스에서 작성되기 때문에 클래스의 인스턴스라고 합니다. 클래스는 개체의 인터페이스, 개체의 공용 여부 그리고 어떠한 상황에서 생성되는지를 정의합니다. 클래스에 대한 설명은 형식 라이브러리에 저장되고 개체 찾아보기로 확인할 수 있습니다.

개체를 사용하려면 개체 변수에 그 참조가 있어야 합니다. 연결 유형은 개체 변수를 사용하여 액세스되는 개체 메서드의 속도를 결정합니다. 개체 변수는 후기 바운드(가장 느림)초기 바운드가 될 수 있습니다. 초기 바운드 변수는 DispID 연결이나 vtable 연결(가장 빠름)이 될 수 있습니다.

속성과 메서드 집합을 인터페이스라고 합니다. Visual Basic 개체의 기본 인터페이스는 세 바인딩 형태를 모두 지원하는 이중 인터페이스입니다. 개체 변수가 강력한 형식 정의(, Dim … As classname)로 되었다면 가장 빠른 형태의 바인딩이 사용됩니다.

기본 인터페이스 외에도 Visual Basic 개체는 다형성을 지원하도록 추가 인터페이스를 구현할 수 있습니다. 다형성으로 각각의 종류에 관계 없이 다양한 종류의 개체를 처리할 수 있습니다. 다중 인터페이스는 구성 요소 개체 모델(Component Object Model COM)의 기능이며, 기존 코드를 유지한 채 새 기능을 추가하여 시간을 낭비하지 않고 프로그램을 발전시킬 수 있습니다.

Visual Basic 클래스는 데이터 인식의 주체가 될 수 있습니다. 클래스는 외부 데이터 원본에 직접 바인딩하여 데이터 소비자가 될 수 있습니다. 또한 다른 개체에 외부 원본의 데이터를 제공하여 데이터 원본이 될 수 있습니다.

끝내기

이 모든 내용에 익숙하다면 이 장의 나머지 내용도 부담없이 살펴 보십시오. 혹시 이 내용에 익숙하지 않다고 해도 걱정할 필요는 없습니다. 이 장의 텍스트 중간 중간에 이 용어들이 가끔 설명되어 있습니다.

 

개체를 포함하는 클래스 찾기

일반 개체 변수(, As Object로 선언한 변수)는 다른 많은 클래스 개체를 포함할 수 있습니다. 마찬가지로 변수는 다른 클래스와 폼을 포함할 수 있는 Visual Basic의 내장된 Form Control 형식으로 선언할 수 있습니다.

이러한 형식의 변수를 사용할 때에는 개체의 클래스에 기초한 다른 동작을 취할 수 있습니다. 예를 들면 어떤 개체는 특정 속성이나 메서드를 지원하지 않을 수 있습니다. Visual Basic은 이것을 해결하기 위해 TypeOf 키워드와 TypeName 함수를 지원합니다.

TypeOf 키워드는 단지 If ... Then ... Else 문 내에서 사용될 수 있습니다. 클래스 이름은 If TypeOf MyControl Is CheckBox Then과 같이 코드에 직접 포함시켜야 합니다.

TypeName 함수는 좀더 복잡합니다. TypeName 함수는 코드 어디에서나 사용할 수 있지만 문자열로 클래스 이름을 반환하기 때문에 문자열 변수로 값을 비교할 수 있습니다.

 

문자열 이름을 사용하여 속성이나 메서드 호출

대부분의 경우 디자인 모드에서 개체의 속성과 메서드를 찾을 수 있으며 이 속성과 메서드를 처리하는 코드를 작성할 수 있습니다. 그러나 어떤 경우에 개체의 속성과 메서드를 미리 알 수 없을 수도 있고 실행 모드에서 사용자가 속성을 지정하거나 메서드를 실행할 수 있도록 지정할 수도 있습니다.

예를 들어 서버 응용 프로그램에 전달하도록 연산자를 입력한 식을 평가하는 클라이언트 응용 프로그램이 있다고 가정합니다. 새 연산자가 필요한 서버에 새로운 함수를 지속적으로 추가하는 경우에는 클라이언트 응용 프로그램을 다시 컴파일하고 다시 배포한 다음 클라이언트 응용 프로그램에서 새 연산자를 사용해야 합니다. CallByName 함수를 사용하여 새 연산자를 문자열로 전달하면 이 클라이언트 응용 프로그램을 변경하지 않아도 됩니다.

CallByName 함수를 사용하면 문자열을 사용하여 실행 모드에서 속성이나 메서드를 지정할 수 있습니다. CallByName 함수의 서명은 다음과 같습니다.

Result = CallByName(Object, ProcedureName, CallType, Arguments())

CallByName의 첫째 인수는 실행할 개체의 이름입니다. 둘째 인수 ProcedureName은 실행할 메서드나 속성 프로시저입니다. CallType 인수는 method(vbMethod), property let(vbLet), property get(vbGet), property set(vbSet)과 같이 실행할 프로시저 형식을 나타내는 상수입니다. 마지막 인수는 선택적 인수이며 프로시저에 대한 모든 인수를 갖는 variant 배열입니다.

SquarRoot 함수가 있는 MathServer 서버 응용 프로그램이 있다고 가정합니다. 응용 프로그램에 두 개의 TextBox 컨트롤이 있습니다. Text1 컨트롤에는 계산할 식이 있으며 Text2 컨트롤을 함수의 이름을 입력하는 데 사용할 경우 아래와 같은 코드를 CommandButton Click 이벤트에 사용하여 Text1의 식에 대한 SquareRoot 함수를 발생시킬 수 있습니다.

Private Sub Command1_Click()
   Text1.Text = CallByName(MathServer, Text2.Text, vbMethod, Text1.Text)
End Sub

Text1 "64 / 4"를 입력하고 Text2 "SquareRoot"를 입력하면 위의 코드는 계산될 식을 포함하는 문자열을 필요한 인수로 갖는 SquareRoot 함수를 발생시킨 후 Text1 16이나 64 4로 나눈 값의 제곱근인 "4"를 반환합니다. Text2에 잘못된 문자열을 입력하거나, 메서드 대신 속성 이름을 문자열로 사용하는 경우 메서드가 추가 인수를 필요로 하는 경우에는 런타임 오류가 발생합니다. CallByName을 사용하여 이러한 오류나 다른 오류가 발생할 것이 예상되면 강력한 오류 처리 코드를 추가해 두는 것이 좋습니다.

경우에 따라 CallByName 함수를 사용하는 것이 좋을 수도 있지만 성능에 대한 유용성을 잘 생각해 보아야 합니다. CallByName을 사용하여 프로시저를 실행하면 후기 바운드 호출보다 약간 느리게 됩니다. 루프에서처럼 반복적으로 호출해야 하는 함수를 실행할 경우에 CallByName 함수를 사용하면 성능에 상당한 영향을 끼칠 수 있습니다.

 

개체에서 여러 작업 수행

한 개체에서 여러 작업을 수행해야 하는 경우가 있습니다. 예를 들어 한 개체에 여러 속성을 설정해야 하는 경우입니다. 이와 같은 경우 필요한 수만큼 문을 사용합니다.

Private Sub Form_Load()
   Command1.Caption = "확인"
   Command1.Visible = True
   Command1.Top = 200
   Command1.Left = 5000
   Command1.Enabled = True
End Sub

모든 문에서 같은 개체 변수 Command1을 사용한다는 것에 주목하기 바랍니다. With...End With 문을 사용하면 이 코드를 읽고 쓰기 쉬울 뿐만 아니라 더욱 효과적으로 실행할 수 있습니다.

Private Sub Form_Load()
   With Command1
      .Caption = "확인"
      .Visible = True
      .Top = 200
      .Left = 5000
      .Enabled = True
   End With
End Sub

With...End With 문을 With...End With 내에 포함시켜 중첩된 With 문을 만들 수도 있습니다.

 

기본 속성 사용

많은 개체에는 기본 속성이 있습니다. 값을 설정할 경우에는 속성을 명확하게 참조하지 않아도 되기 때문에 기본 속성을 사용하여 코드를 단순화할 수 있습니다. Value 속성이 기본 속성인 개체의 경우 이 두 문은 같은 기능을 합니다.

object = 20

그리고

object.Value = 20

기본 속성이 어떻게 사용되는지를 보려면 폼에 CommandButton TextBox를 그린 다음 CommandButton Click 이벤트에 다음 문을 추가합니다.

Text1 = "안녕하십니까?"

응용 프로그램을 실행하고 CommandButton을 누릅니다. Text TextBox의 기본 속성이기 때문에 TextBox "안녕하십니까?"가 표시됩니다.

개체 변수와 함께 기본 속성 사용

개체 참조가 개체 변수에 저장되면 기본 속성을 계속 사용할 수 있으며 다음 코드에서 확인할 수 있습니다.

Private Sub Command1_Click()
   Dim obj As Object
   ' 개체 변수에 Text1에 대한 참조를 저장합니다.
   '   variable.
   Set obj = Text1
   ' 기본 속성값(Text)을 설정합니다.
   obj = "안녕하십니까?"
End Sub

위의 코드에서 obj = "안녕하십니까?"obj.Text = "안녕하십니까?"라고 입력하는 것과 같습니다.

Variant와 함께 기본 속성 사용

개체 변수에 저장하지 않고 Variant 형식의 변수에 개체 참조를 저장하면 기본 속성을 액세스하는 것이 달라집니다.

예를 들어 Variant 형식의 참조를 사용하여 Text1의 기본 속성을 읽을 수 있지만 기본 속성에 "goodbye" 문자열을 할당할 수 없습니다. 대신 Variant 형식은 문자열에 대한 참조로 바뀝니다.

이것이 어떻게 수행되는지 보려면 앞의 예제에서 CommandButton Click 이벤트에 다음 코드를 입력하십시오.

Private Sub Command1_Click()
   Dim vnt As Variant
   ' "안녕하십니까?"에 기본 속성(텍스트)을 설정합니다.
   Text1 = "안녕하십니까?"
   ' Variant Text1에 대한 참조 위치를 지정합니다.
   Set vnt = Text1
   ' Text1의 기본 속성을 표시하고,
   ' Variant에 개체 참조가 있는 것을 나타냅니다.
   MsgBox vnt, , "IsObject? " & IsObject(vnt)
   ' Text1의 기본 속성을 설정합니다.
   vnt = "goodbye"
   MsgBox vnt, , "IsObject? " & IsObject(vnt)
End Sub

응용 프로그램을 실행하고 CommandButton을 누르면 우선 Text1의 현재 기본 속성값, "안녕하십니까?"를 표시하는 메시지 상자가 나타납니다. 이는 Text1을 봄으로써 검증할 수 있습니다. 메시지 상자 캡션은 Variant에 개체 참조(, Text1에 대한 참조)가 있는지 확인해 줍니다.

메시지 상자에서 확인 단추를 누르면 Text1에 대한 참조를 없애고 "goodbye" Variant에 할당됩니다. 그러면 Variant의 내용을 나타내는 다른 메시지 상자가 나타나며, 보이는 것과 같이 현재 Text1.Text 값과 일치하지 않습니다.

메시지 상자 캡션에서 Variant에 더 이상 개체 참조가 없고 "goodbye" 문자열이 있는 것을 확인할 수 있습니다.

추가 정보   Variant와 기타 데이터 형식에 대한 자세한 내용은 "프로그래밍 기초" "변수, 상수, 데이터 형식 개요"를 참조하십시오.

Variants로 개체를 사용하는 다른 방법은 "Visual Basic Collection 개체"를 참조하십시오.

 

개체 배열 작성

다른 데이터 형식의 배열을 선언하고 사용하는 것처럼 개체 유형의 배열을 선언하고 사용할 수 있습니다. 이 배열은 고정 배열이거나 동적 배열일 수 있습니다.

폼 변수의 배열

다른 데이터 형식의 배열을 선언하는 것과 같은 방법으로 Private, Dim, ReDim, Static, Public 등과 함께 폼 배열을 선언할 수 있습니다. New 키워드와 함께 배열을 선언할 경우 배열에서 요소를 사용하는 것처럼 Visual Basic에서 자동으로 배열의 각 요소에 새 폼 인스턴스를 작성합니다.

Private Sub Command1_Click ()
   Dim intX As Integer
   Dim frmNew(1 To 5) As New Form1
   For intX = 1 To 5
      frmNew(intX).Show
      frmNew(intX).WindowState = vbMinimized
      ' 폼이 정상적 크기로 나타나지 않고
      ' 바로 최소화되기를 원한다면
      ' 위의 두 줄 순서를 반대로 합니다.
   Next
End Sub

위에 있는 코드를 실행하기 위해 CommandButton을 누르면 최소화된 다섯 개의 Form1 인스턴스가 작성됩니다.

메모   작업 표시줄에 Form1여섯번 나타납니다. 코드를 시작한 Form1 인스턴스는 최소화되지 않습니다.

컨트롤 변수의 배열

다른 데이터 형식의 배열을 선언하는 것과 같은 방법으로 Private, Dim, ReDim, Static, Public 등과 함께 컨트롤의 배열을 선언할 수 있습니다. 그러나 폼 배열과는 달리 New 키워드와 함께 컨트롤 배열을 선언할 수 없습니다. 예를 들어 특정 컨트롤 유형이 되도록 배열을 선언할 수 있습니다.

ReDim ActiveImages(10) As Image

특정 컨트롤 유형이 되도록 배열을 선언하면 해당 유형의 컨트롤만 배열에 할당할 수 있습니다. 앞의 선언을 예를 들면, 이미지 컨트롤만 배열에 할당할 수 있습니다. 하지만 이 이미지 컨트롤을 다른 폼에서 가져올 수는 있습니다.

이것과 달리 다양한 컨트롤 유형을 포함할 수 있는 Controls 컬렉션 배열의 참조는 모두 같은 폼에 있어야 합니다.

다른 방법으로는 포괄적인 컨트롤 변수의 배열을 선언할 수 있습니다. 예를 들어 특정 컨트롤에 끌어놓은 모든 컨트롤을 검사하여 각 컨트롤을 해당 컨트롤에 한번만 끌어 놓으려면 끌어서 놓은 각 컨트롤에 대한 참조가 있는 컨트롤 변수의 동적 배열을 유지함으로써 이것을 가능하게 할 수 있습니다.

Private Sub List1_DragDrop(Source As VB.Control, _
      X As Single, Y As Single)
   Dim intX As Integer
   Static intSize As Integer
   Static ctlDropped() As Control
   For intX = 1 To intSize
      ' 끌어 놓은 컨트롤이 배열에 있으면
      ' 이미 여기에 한 번 끌어 놓은 것입니다.
      If ctlDropped(intX) Is Source Then
         Beep
         Exit Sub
      End If
   Next
   ' 배열을 확대합니다.
   intSize = intSize + 1
   ReDim Preserve ctlDropped(intSize)
   ' 끌어 놓은 컨트롤에 대한 참조를 저장합니다.
   Set ctlDropped(intSize) = Source
   ' ListBox에 컨트롤 이름을 추가합니다.
   List1.AddItem Source.Name
End Sub

예제는 Is 연산자로 컨트롤 인수와 컨트롤 배열의 변수를 비교하는 것을 보여줍니다. Is 연산자는 Visual Basic 개체 참조를 식별하는 검사에 사용할 수 있습니다. 비교한 참조 두 개가 같은 개체에 대한 것이라면 Is 연산자는 True를 반환합니다.

예제에서 Source 인수의 개체 참조를 배열 요소에 할당하는데 Set 문을 사용한 것에 주의하십시오.

추가 정보   언어 참조 "Is 연산자"를 참조하십시오.

배열에 대한 자세한 내용은 "배열", "동적 배열"을 참조하십시오.

개체를 쉽게 관리하는 방법에 대한 내용은 이 장 뒷부분의 "개체 컬렉션 작성"을 참조하십시오.

 

개체 컬렉션 작성

컬렉션은 유용한 개체 관리 방법을 제공합니다. 배열과 달리 Collection 개체는 구성원을 추가하고 삭제할 때 다시 선언하지 않아도 됩니다.

예를 들어 특정 컨트롤에 끌어 놓은 각 컨트롤에 대한 참조가 있는 컨트롤 변수의 동적 배열을 유지함으로써 특정 컨트롤에 끌어 놓은 모든 컨트롤을 추적할 수 있지만 컨트롤을 한 번만 놓을 수 있습니다.

Private Sub List1_DragDrop(Source As VB.Control, _
X As Single, Y As Single)
   Dim vnt As Variant
   Static colDroppedControls As New Collection
   For Each vnt In colDroppedControls
      ' 끌어놓은 컨트롤이 컬렉션에 있으면
      ' 이미 여기에 한 번 끌어 놓은 것입니다.
      If vnt Is Source Then
         Beep
         Exit Sub
      End If
   Next
   ' 끌어 놓은 컨트롤에 대한 참조를 저장합니다.
   colDroppedControls.Add Source
   ' ListBox에 컨트롤 이름을 추가합니다.
   List1.AddItem Source.Name
End Sub

예제는 Is 연산자로 colDroppedControls 컬렉션의 개체 참조와 끌어 놓은 컨트롤의 참조를 가지는 이벤트 인수를 비교하는 것을 보여줍니다. Is 연산자는 Visual Basic 개체 참조를 식별하는 검사에 사용할 수 있습니다. 같은 개체의 다른 두 참조를 비교하면 Is 연산자는 True를 반환합니다.

예제에서 컬렉션에 끌어 놓은 컨트롤 참조 위치를 지정하는데 Collection 개체의 Add 메서드도 사용합니다.

배열과 달리 Collection은 개체 자체입니다. Collection 클래스의 인스턴스는 코드에서 처음 변수를 참조할 때 작성되기 때문에 colDroppedControls 변수는 As New로 선언됩니다. 이벤트 프로시저가 끝났을 때 Collection 개체가 파괴되지 않도록 Static 변수로 선언합니다.

추가 정보   언어 참조 "Is 연산자"를 참조하십시오.

Collection 개체의 속성과 메서드는 이 장 뒷부분의 "Visual Basic Collection 개체"에서 자세히 설명합니다.

위의 코드와 배열 사용에 필요한 코드를 비교하려면 이 장 앞부분의 "개체 배열 작성"을 참조하십시오.

Collection 개체를 사용자 컬렉션 클래스로 확장하여 더욱 생산적인 컬렉션을 작성하는 방법에 대한 자세한 내용은 이 장 뒷부분의 "사용자 고유 컬렉션 클래스 작성"을 참조하십시오.

이 장 앞부분의 "Visual Basic 개체에 대하여 알아야 할 사항"에서는 개체가 작성되고 소멸되는 과정을 설명합니다.

 

Visual Basic Collection 개체

컬렉션은 관련 항목 집합을 그룹화하는 방법입니다. 컬렉션은 프로그램에서 로드한 폼(Forms 컬렉션)이나 폼의 모든 컨트롤(Controls 컬렉션) 등 많은 것을 관리하기 위해 Visual Basic에서 사용합니다.

Visual Basic에서는 사용자 컬렉션을 정의할 수 있도록 포괄적인 Collection 클래스를 제공합니다. 필요한 만큼 Collection 개체(Collection 클래스의 인스턴스)를 작성할 수 있습니다. 이 장 뒷부분의 "사용자 고유 컬렉션 클래스 작성" "개체 모델"의 설명처럼 Collection 개체를 사용자 컬렉션 클래스와 개체 모델의 기초로 사용할 수 있습니다.

예를 들어 컬렉션은 다중 폼을 관리하는 좋은 방법입니다. "사용자 인터페이스 작성" "MDI 응용 프로그램"에서는 문서 창을 얼마든지 열 수 있는 응용 프로그램에 대해 설명합니다. 다음 코드는 컬렉션 개체의 Add 메서드를 사용하여 사용자가 작성한 MDI 하위 창 목록을 유지하는 방법을 나타냅니다. 이 코드는 MDIChild 속성이 True로 설정된 mdiDocument라는 이름을 가진 폼이 있다고 가정합니다.

' 상위 MDIForm의 모듈 수준 컬렉션
Public colDocuments As New Collection
 
'  MDI 하위 문서 폼 작성 코드
Private Sub mnuFileNew()
   Dim f As New mdiDocument
   Static intDocumentNumber As Integer
   intDocumentNumber = intDocumentNumber + 1
   ' 다음 줄은 폼을 작성합니다.
   f.Caption = "문서" & intDocumentNumber
   ' 컬렉션에 개체 참조를 추가합니다.
   colDocuments.Add f
   f.Show
End Sub

colDocuments 컬렉션은 mdiDocument 폼의 인스턴스만 포함하면서 제공된 Forms 컬렉션의 부분 집합처럼 행동합니다. 컬렉션 크기는 각각의 새 폼이 추가되면 자동으로 조정됩니다. For Each ... Next를 사용하여 컬렉션의 내용을 추적할 수 있습니다. 검색할 수 있는 키를 폼에 지정하고자 하는 경우 이 단락 뒷부분의 설명처럼 텍스트 문자열을 Add 메서드의 둘째 매개 변수로 적용할 수 있습니다.

colDocuments 변수 선언에서 New 키워드를 사용하면 코드에서 변수를 처음 참조할 때 Collection 개체가 작성됩니다. Collection은 데이터 형식이 아닌 클래스이기 때문에 인스턴스를 작성하고 변수에서 해당 인스턴스(개체)에 대한 참조를 유지해야 합니다.

다른 개체처럼 Collection 개체는 참조가 있는 마지막 변수가 Nothing으로 설정되거나 범위를 벗어나면 소멸됩니다. 그리고 포함하는 모든 개체 참조를 해제합니다. 그 때문에 프로그램 사용 기간 동안 존재하도록 colDocuments 변수가 상위 MDIForm에 선언됩니다.

메모   컬렉션을 폼 추적에 사용할 경우 폼을 언로드한 후에 컬렉션의 Remove 메서드를 사용하여 컬렉션의 개체 참조를 삭제합니다. 폼에 대한 참조가 존재하고 Collection 개체가 유지하고 있는 참조가 개체 변수에 있는 참조만큼 유효한 동안은 폼에서 사용 중인 메모리를 사용할 수 없습니다.

Collection 개체의 구성 요소

Collection 개체는 Variant 형식에 각 항목을 저장합니다. 따라서 Collection 개체에 추가할 수 있는 목록은 Variant에 저장할 수 있는 목록과 같습니다. 여기에는 표준 데이터 형식, 개체, 배열 등이 포함되지만 사용자 정의 형식은 포함되지 않습니다.

Variants는 저장된 것에 관계 없이 항상 16바이트를 사용하므로 Collection 개체보다 배열을 사용하는 것이 더 효율적인 것이 사실입니다. 그러나 Collection 개체를 ReDim하지 않는 것이 결과적으로 더욱 완전하고 유지하기 좋은 코드가 됩니다. 게다가 Collection 개체에는 배열에 없는 매우 빠른 키 검색 도구가 있습니다.

메모   데이터가 실제로 다른 위치에 저장되어 있을 경우에도 Variant는 항상 정확히 16바이트를 사용합니다. 예를 들어 문자열이나 배열을 Variant에 할당하면 Variant에 문자열이나 배열 데이터 사본의 포인터가 포함됩니다. 32비트 시스템에서 Variant 4바이트만 포인터에 사용되고 Variant 내부에는 실제로 데이터가 없습니다.

개체를 저장하면 개체 변수에서처럼 Variant에 개체 참조가 포함됩니다. 문자열과 배열에서처럼 Variant 4바이트만 사용됩니다.

숫자 데이터 형식은 Variant 안에 저장됩니다. 데이터 형식에 관계 없이 Variant는 계속 16바이트만 사용합니다.

Variants의 크기에도 불구하고 Collection 개체로 위에서 이야기한 모든 데이터 형식을 저장하기 위해 사용됩니다. Collection 개체로 Variants에 항목을 저장하는 대신 더욱 완전하고 관리하기 좋은 코드를 작성할 수 있습니다.

Collection 개체의 속성과 메서드

Collection 개체는 컬렉션에서 항목을 삽입, 삭제, 검색하는데 사용할 수 있는 속성과 메서드를 가집니다.

속성이나 메서드

설명

Add 메서드

컬렉션에 항목을 추가합니다.

Count 속성

컬렉션 항목의 갯수를 반환합니다.

읽기 전용

Item 메서드

인덱스나 키별로 항목을 반환합니다.

Remove 메서드

인덱스나 키별로 컬렉션에서 항목을 삭제합니다.


 

이 속성과 메서드는 컬렉션의 가장 기본적인 기능을 제공합니다. 예를 들어 Add 메서드는 컬렉션에 추가될 개체 유형을 확인할 수 없으므로 컬렉션에 한 종류의 개체만 있는지 확인할 수 없습니다. 이 장 뒷부분의 "사용자 고유 컬렉션 클래스 작성"의 설명처럼 사용자 컬렉션 클래스를 작성하여 더욱 생산적인 기능과 추가 속성, 메서드, 이벤트 등을 제공할 수 있습니다.

컬렉션에서 추가, 삭제, 검색하는 기본 기능은 키와 인덱스에 의존합니다. String 값으로 이름, 운전 면허 등록 번호, 주민 등록 번호 또는 단순히 String으로 변환된 정수 등이 될 수 있습니다. 이 단락 뒷부분에서 설명한 것처럼 Add 메서드로 키를 항목과 연결할 수 있습니다.

인덱스는 범위가 1과 컬렉션의 항목 수 사이인 Long 데이터입니다. beforeafter로 명명된 인수를 사용하여 항목의 인덱스 초기값을 제어할 수 있지만 다른 항목이 추가되거나 삭제될 때 값이 변경됩니다.

메모   "Visual Basic 컬렉션"의 설명처럼 인덱스가 1로 시작하는 컬렉션을 "1 기준"이라고 합니다.

인덱스를 사용하여 컬렉션의 항목을 제어할 수 있습니다. 예를 들어 다음 코드는 mcolEmployees 변수에 Collection 개체 참조가 있다는 가정하에 Employee 개체 컬렉션에 저장한 모든 직원에게 10%의 인상을 적용하는 두 가지 방법을 나타냅니다.

Dim lngCt As Long
For lngCt = 1 To mcolEmployees.Count
   mcolEmployees(lngCt).Rate = _
      mcolEmployees(lngCt).Rate * 1.1
Next
 
Dim emp As Employee
For Each emp In mcolEmployees
   emp.Rate = emp.Rate * 1.1
Next

   성능을 향상시키려면 For Each를 사용하여 Collection 개체의 항목을 제어하십시오. For Each는 인덱스로 제어하는 것보다 훨씬 빠릅니다. 이것은 모든 컬렉션 구현에 적용되는 것이 아니며 컬렉션에서 데이터를 내부적으로 저장하는 방법에 따라 다릅니다.

컬렉션에 항목 추가

Add 메서드로 컬렉션에 항목을 추가합니다. 구문은 다음과 같습니다.

Sub Add (item As Variant [, key As Variant] [, before As Variant]

[, after As Variant] )

예를 들어 작업 주문 ID 속성을 키로 사용하여 작업 주문 컬렉션에 작업 주문 개체를 추가하려면 다음과 같이 기록합니다.

colWorkOrders.Add woNew, woNew.ID

위의 예에서 ID 속성은 문자열로 간주합니다. 속성이 숫자(예를 들어 Long)일 경우 CStr 함수를 사용하여 키에 필요한 문자열 값으로 변환합니다.

colWorkOrders.Add woNew, CStr(woNew.ID)

Add 메서드는 명명된 인수를 지원합니다. 현재 추가하려는 항목을 셋째 요소로 추가하려면 다음과 같이 기록합니다.

colWorkOrders.Add woNew, woNew.ID, after:=2

beforeafter 이름의 인수를 사용하여 개체 컬렉션의 순서를 관리할 수 있습니다. 예를 들어 Collection 개체는 1부터 시작하기 때문에 before:=1은 컬렉션 시작 부분에 항목을 삽입합니다.

컬렉션에서 항목 삭제

Remove 메서드를 사용하여 컬렉션에서 항목을 삭제합니다. 구문은 다음과 같습니다.

object.Remove index

index 인수는 삭제할 항목 위치나 항목 키가 될 수 있습니다. 컬렉션에서 제3의 요소 키가 "W017493"일 경우 다음 두 문장 중 하나를 사용하여 삭제할 수 있습니다.

colWorkOrders.Remove 3

-또는-

colWorkOrders.Remove "W017493"

컬렉션에서 항목 검색

Item 메서드를 사용하여 컬렉션에서 특정 항목을 검색합니다. 구문은 다음과 같습니다.

[Set] variable = object.Item(index)

Remove 메서드에서처럼 인덱스는 컬렉션 위치나 항목 키가 될 수 있습니다. Remove 메서드와 같은 예제를 사용하여 다음 두 문장 중 하나로 컬렉션에서 셋째 요소를 추출할 수 있습니다.

Set woCurrent = colWorkOrders.Item(3)

-또는-

Set woCurrent = colWorkOrders.Item("W017493")

정수를 키로 사용할 경우 CStr 함수를 사용하여 문자열로 변환한 다음 Item이나 Remove 메서드로 보내야 합니다. Collection 개체는 항상 정수를 인덱스로 간주합니다.

메모   전달할 값이 인덱스인지 키인지를 Collection 개체가 결정하지 않도록 하십시오. 키로 사용하려는 변수가 String이 아닐 경우 CStr을 사용하여 변환합니다. 인덱스로 사용하고자 하는 값이 정수 데이터 형식이 아닐 경우 CLng를 사용하여 변환합니다.

기본 메서드: Item 메서드

Item 메서드는 Collection 개체의 기본 메서드이므로 컬렉션의 개체에 액세스할 때 생략할 수 있습니다. 따라서 이전 코드 예제를 다음과 같이 기록할 수도 있습니다.

Set woCurrent = colWorkOrders(3) 

-또는-

Set woCurrent = colWorkOrders("W017493")

중요   Collection 개체는 요소를 추가하거나 삭제할 경우 자동으로 인덱스 수를 관리합니다. 따라서 주어진 요소의 인덱스는 그때 그때 변경됩니다. 인덱스 값을 저장한 후 나중에 프로그램에서 같은 요소를 검색할 수 없을 수도 있습니다. 이후에 같은 요소를 제어하기 위한 목적이라면 키 값을 사용해야 합니다.

Item 메서드를 사용하여 속성과 메서드 실행

개체 참조를 사용하기 위해 컬렉션에서 검색하거나 개체 변수에 저장할 필요는 없습니다. 컬렉션에 있는 동안 참조를 사용할 수 있습니다.

예를 들어 위의 코드에서 WorkOrder 개체에 Priority 속성이 있다고 가정할 경우 다음 두 문장은 모두 작업 주문의 우선 순위를 설정합니다.

colWorkOrders.Item("W017493").Priority = 3
colWorkOrders("W017493").Priority = 3

Visual Basic은 왼쪽에서 오른쪽으로 식을 계산하기 때문에 이와 같은 작업을 할 수 있습니다. Item 메서드(명시적이든 아니든)가 사용되면 Visual Basic에서는 지시된 항목(이 경우에는 키가 W017493WorkOrder 개체)에 대한 참조를 받아 이 참조로 나머지 코드 줄을 실행합니다.

   컬렉션에서 개체의 속성이나 메서드를 여러 번 불러오려면 우선 명확히 형식 선언된 개체 변수에 개체 참조를 복사합니다. Collection 개체는 Variants에 항목을 저장하기 때문에 컬렉션에 있는 동안 개체 참조를 사용하는 것보다 입력된 개체 변수(: Dim woCurrent As WorkOrder)에 복사한 후에 사용하는 것이 더 빠릅니다. Variants의 개체 참조는 항상 가장 느리게 연결됩니다.

추가 정보   Collection 개체는 배열을 대신하여 다양한 일상적인 프로그래밍 작업에서 유용하게 사용할 수 있습니다. "프로그래밍 추가 정보"에서 "배열 대신 컬렉션 사용"을 참조하십시오.

Visual Basic에서는 다양한 기본 제공 컬렉션을 제공합니다. Collection 개체와 비교하려면 "Visual Basic 컬렉션"을 참조하십시오.

 

Visual Basic 컬렉션

컬렉션이란 "Visual Basic Collection 개체"에서 관련 개체를 그룹화하는 방법으로 정의되어 있습니다. 그러나 다양한 해석이 가능하며, 이 정의 외의 추가 개념이 있습니다.

사실 컬렉션을 비교할 때 보게 되는 것처럼 Visual Basic에서 제공하는 컬렉션 종류 사이에도 많은 차이점이 있습니다. 예를 들어 다음 코드에서는 오류가 발생합니다.

Dim col As Collection
Set col = Forms      ' 오류가 발생하였습니다!

왜 오류가 발생했을까요? Forms 컬렉션은 컬렉션 개체입니다. col 변수는 As Collection으로 선언된 변수입니다. Forms에 대한 참조를 col 변수에 할당할 수 없을까요?

그 이유는 Collection 클래스와 Forms 컬렉션은 다형적이지 않기 때문입니다. , 개별 코드에 기초하여 개발되었기 때문에 서로 교환할 수 없습니다. 그 둘은 서로 메서드가 다르고, 개체 참조 방식이나 인덱스 값을 다르게 가지게 됩니다.

이것은 실제로 컬렉션 구현 가능성 중의 하나를 나타내기 때문에 Collection 클래스 이름이 잘못 사용된 경우가 됩니다. 이 항목에서는 생길 수 있는 몇 가지 구현 차이점을 알아봅니다.

0부터 시작하는 컬렉션과 1부터 시작하는 컬렉션

컬렉션은 인덱스 종류에 따라 0부터 시작하거나 1부터 시작하는 컬렉션이 됩니다. 짐작대로 컬렉션의 첫째 항목 인덱스가 0부터 시작하는 컬렉션은 영(0)이고 1부터 시작하는 컬렉션은 1입니다. 0부터 시작하는 컬렉션의 예로는 Forms Controls 컬렉션이 있습니다. Collection 개체는 1부터 시작하는 컬렉션입니다.

Visual Basic에 처음부터 있었던 컬렉션은 0부터 시작하는 컬렉션과 비슷한 반면 최근에 추가된 컬렉션은 1부터 시작하는 컬렉션에 더 가깝습니다. 1부터 시작하는 컬렉션은 인덱스 범위가 1부터 Count(컬렉션에서 항목 수를 반환하는 Count 속성)까지 이므로 더욱 직관적으로 사용할 수 있습니다.

이와 대조적으로 0부터 시작하는 컬렉션은 0부터 Count 속성보다 1이 작은 범위를 가집니다.

인덱스와 키 값

다양한 Visual Basic 컬렉션은 Visual Basic Collection 개체처럼 숫자 인덱스나 문자열 키를 사용하여 항목을 액세스할 수 있습니다. 그러나 Visual Basic Collection 개체는 키를 지정하지 않고 항목을 추가할 수 있습니다.

이와는 대조적으로 Forms 컬렉션은 폼과 연관된 고유 문자열 값이 없기 때문에 숫자 인덱스만 허용합니다. 예를 들어 캡션이 같은 다중 폼이나 Name 속성이 같은 폼을 여러 개 로드할 수 있기 때문입니다.

항목 추가와 삭제

또한 컬렉션은 항목 추가 여부와 항목 추가 방법에 따라 다릅니다. 예를 들어 Visual Basic 코드를 사용하여 Printers 컬렉션에 프린터를 추가할 수 없습니다.

Collection 개체는 일반적인 프로그래밍 도구이기 때문에 다른 컬렉션보다 더 유동적입니다. 컬렉션에 항목을 추가하는데 사용할 수 있는 Add 메서드와 항목을 삭제하는 Remove 메서드가 있습니다.

이와 대조적으로 폼을 Forms 컬렉션에 추가하는 유일한 방법은 폼을 로드하는 것입니다. New 연산자를 사용하여 폼을 작성하거나 As New로 선언된 변수를 참조하여 폼을 작성할 경우 Load 문을 사용하여 로드해야 Forms 컬렉션에 추가할 수 있습니다.

Forms Controls 컬렉션에는 Remove 메서드가 없습니다. Load Unload 문을 사용하여 직접 이 컬렉션에서 폼과 컨트롤을 추가하고 삭제합니다.

내부를 더 자세히 살펴보면

위에서 설명한 것처럼 폼을 로드해야 Forms 컬렉션에 추가할 수 있습니다. 따라서 가장 정확한 Forms 컬렉션의 사양은 프로그램에 현재 로드해온 모든 폼이 있는 것입니다.

그러나 완전히 정확하지는 않습니다. 프로젝트에서 Microsoft Office와 호환되도록 포함된 Microsoft Forms을 사용할 경우 UserForms이라는 개별 컬렉션에서 이 폼을 찾을 수 있습니다. 따라서 Forms 컬렉션에는 프로그램에 현재 불러온 모든 Visual Basic 이 있습니다.

Collection 클래스 내용은 매우 정확하게 지정됩니다. Variant에 저장할 수 있는 것이라면 어떤 것이라도 가능합니다. 따라서 Collection 개체는 개체나 정수를 포함할 수 있지만 사용자 정의 형식은 포함할 수 없습니다.

유감스럽게도 이 사양은 많은 범위를 차지하므로 주어진 Collection 클래스의 인스턴스는 데이터 형식, 배열, 개체 등 잡다한 것을 저장할 수 있습니다.

   "사용자 고유 컬렉션 클래스 작성"에서 설명한 것처럼 사용자 고유 컬렉션 클래스를 작성하는 가장 중요한 이유는 컬렉션의 내용을 제어할 수 있는 것으로 형식 안전이라는 개념입니다.

컬렉션 열거

For Each … Next를 사용하면 컬렉션이 0부터 시작하는지 1부터 시작하는지에 관계 없이 컬렉션 내의 항목을 열거할 수 있습니다. 물론 Visual Basic에서 For Each … Next를 사용하여 배열에 항목을 열거할 수 있기 때문에 이것이 컬렉션의 특징이라고 할 수는 없습니다.

For Each … Next를 작동시키는 것은 열거자(enumerator)라는 작은 개체입니다. 열거자는 컬렉션에서 있는 장소를 추적하고 필요할 때 다음 항목을 반환합니다.

배열을 열거하려면 Visual Basic은 실행 시간에 열거자 개체 배열을 작성합니다. 컬렉션에는 고유의 열거자 개체가 있으며 필요시 작성됩니다.

열거자는 항목을 건너뛰지 않습니다

Visual Basic 컬렉션의 열거자는 항목을 건너뛰지 않습니다. 예를 들어 "A", "B", "C"가 있는 컬렉션을 열거하는 중간에 "B"를 삭제할 경우 Visual Basic 컬렉션은 "C"로 건너뛰지 않습니다.

열거자는 추가된 항목을 확인하지 못할 수 있습니다

열거하는 동안 항목을 컬렉션에 추가할 경우 일부 열거자는 추가된 항목을 포함시키고 일부는 포함시키지 않습니다. 예를 들어 Forms 컬렉션은 열거하는 동안 로드해온 폼을 열거하지 못합니다.

컬렉션 개체의 경우는 끝부분에 항목 추가를 허용할 경우 Collection 개체는 열거하는 동안 추가한 항목을 열거합니다. 따라서 다음 고리는 끝나지 않고 계속되며 <Ctrl+Break>를 눌러야 끝납니다.

Dim col As New Collection
Dim vnt As Variant
col.Add "Endless"
col.Add "Endless"
For Each vnt In col
   MsgBox vnt
   col.Add "Endless"
Next

반면에 컬렉션 시작 부분에 추가하는 항목은 열거에 포함되지 않습니다.

Dim col As New Collection
Dim vnt As Variant
col.Add "Will be enumerated"
For Each vnt In col
   MsgBox vnt
   ' 시작 부분에 항목을 추가합니다.
   col.Add "Won't be enumerated", Before:=1
Next

열거자의 정의

For Each … Next가 시작할 때마다 새 열거자를 생성함으로써 컬렉션은 열거가 중첩되는 것을 허용합니다. 예를 들어 mcolStrings 변수에 Collection 개체에 대한 참조가 있고 컬렉션에는 문자열만 있을 경우 다음 코드는 다른 두 문자열 조합 모두를 인쇄합니다.

Dim vnt1 As Variant
Dim vnt2 As Variant
For Each vnt1 In mcolStrings
   For Each vnt2 In mcolStrings
      If vnt1 <> vnt2 Then
         Debug.Print vnt1 & " " & vnt2
      End If
   Next
Next

추가 정보   이 장 뒷부분의 "사용자 고유 컬렉션 클래스 작성"을 참조하십시오.

 

사용자 고유 컬렉션 클래스 작성

컬렉션을 사용한 개체 포함을 구현할 수 있는 일반 접근 방법이 세 가지 있습니다. "개체 모델"에서 설명한 SmallBusiness 개체의 Employees 컬렉션에서 이 컬렉션을 구현하려면 다음을 수행해야 합니다.

  • SmallBusiness 클래스 모듈에서 Employees 변수를 As Collection으로 선언하고 Public으로 설정합니다. 이것이 쉬운 해결책입니다.

     
  • SmallBusiness 클래스 모듈에서 mcolEmployees 변수를 As Collection으로 선언하고 Private으로 설정합니다. SmallBusiness 개체에 개체 추가와 삭제를 위한 일련의 메서드를 제공합니다. 이것은 세 가지 방법 중 적어도 개체 지향적이기는 합니다.

     
  • 이 장 뒷부분에서 설명할 Employees라는 컬렉션 클래스 모듈을 작성하여 사용자 컬렉션 클래스를 구현합니다. SmallBusiness 개체에 Employees 클래스의 읽기 전용 속성을 설정합니다.

전략은 견고함 증가 순서대로 나열되어 있습니다. 초가집, 나무집, 벽돌집 등의 접근법으로 특성을 나타낼 수 있습니다.

  • 공용 컬렉션 예제: 초가집   Collection 개체의 지나친 유연성은 KitchenSink 개체를 포함한 모든 것을 Collection에 추가할 수 있음을 드러냅니다.

     
  • 전용 컬렉션 예제: 나무집   Collection 개체를 전용으로 설정하여 약간의 견고함을 얻을 수 있지만 컬렉션에서 For Each Next를 사용할 수 없게 됩니다.

     
  • 사용자 컬렉션 클래스 작성: 벽돌집   사용자 컬렉션 클래스를 작성하면 캡슐화의 견고함을 제공하고 덧붙여 For Each Next를 다시 사용할 수 있게 됩니다.

     
  • 우수한 개체 지향 디자인으로 얻는 이익   Collection 클래스를 사용하면 더욱 완벽하고 안전한 코드가 됩니다.

 

공용 컬렉션 예제: 초가집

예제를 작성하려면 새 프로젝트를 열고 두 클래스 모듈을 삽입합니다. 그림 9.13과 같이 다섯 개의 명령 단추, 목록 상자, 두 입력란, 폼의 두 레이블 등을 그립니다.

그림 9.13   Employees 컬렉션 예제

다음은 이 예제 설정에 필요한 속성값을 나열하는 표입니다.

개체

속성

설정

클래스 모듈

Name

Employee

클래스 모듈

Name

SmallBusiness

Caption

Employees 컬렉션

첫째 command button

Caption
Name

추가
cmdAddEmployee

둘째 command button

Caption
Name

삭제
cmdDeleteEmployee

셋째 command button

Caption
Name

목록 새로 고침(Refresh List)
cmdListEmployees

넷째 command button

Caption
Name

오류(Trouble)
cmdTrouble

다섯째 command button

Caption
Name

닫기
cmdClose

첫째 label 컨트롤

Caption

이름

둘째 label 컨트롤

Caption

급여

첫째 text box

Name
Text

TxtName
(
공백)

둘째 text box

Name
Text

txtSalary
(
공백)

List Box

Name

LstEmployees


 

Employee 클래스 모듈에서 다음 선언과 속성 프로시저를 추가합니다.

Option Explicit
' mployee 클래스의 속성
Public Name As String
Public Salary As Long   
 
' 한 번 쓰기 ID 속성의 전용 데이터
Private mstrID As String
 
Property Get ID() As String
   ID = mstrID
End Property
 
' 처음 ID 속성이 설정되면 정적 부울도 설정됩니다.
' 다음 호출은 아무 것도 수행하지 않습니다.
' 대신 오류를 일으키는 것이 더 좋을 것입니다.
Property Let ID(strNew As String)
   Static blnAlreadySet As Boolean
   If Not blnAlreadySet Then
      blnAlreadySet = True
      mstrID = strNew
   End If
End Property

ID 속성은 컬렉션에서 Employee 개체를 검색하거나 삭제하는 키이므로 한 번 설정하고 나면 절대 변경하면 안됩니다. 처음 속성이 설정될 때 True로 설정된 Static 부울 변수와 함께 완성됩니다. Property Get이 있기 때문에 속성을 언제든지 읽을 수 있습니다.

SmallBusiness 클래스 모듈에 다음 선언을 추가합니다. 컬렉션 개체는 처음 Employees 변수가 코드에서 참조될 때 작성됩니다.

Option Explicit
Public Employees As New Collection

모든 작업을 수행하는 폼

여분의 코드는 모두 폼 모듈로 이동합니다. 선언 영역에 다음 선언을 추가합니다.

Option Explicit
Public sbMain As New SmallBusiness

cmdEmployeeAdd_Click 이벤트의 코드는 컬렉션에 구성원을 추가합니다.

Private Sub cmdEmployeeAdd_Click()
   Dim empNew As New Employee
   Static intEmpNum As Integer
   ' With를 사용하여 코드를 더 빠르고 간결하게
   ' 만듭니다(.ID empNew.ID).
   With empNew
      ' 새 직원의 고유 ID를 만듭니다.
      intEmpNum = intEmpNum + 1
      .ID = "E" & Format$(intEmpNum, "00000")
      .Name = txtName.Text
      .Salary = CDbl(txtSalary.Text)
      ' ID 속성을 키로 사용하여 컬렉션에
      ' Employee 개체 참조를 추가합니다.
      sbMain.Employees.Add empNew, .ID
   End With
   txtName.Text = ""
   txtSalary.Text = ""
   ' 목록 새로 고침 단추를 누릅니다.
   cmdListEmployees.Value = True
End Sub

cmdListEmployees_Click 이벤트 프로시저의 코드는 For Each ... Next 문을 사용하여 ListBox 컨트롤에 모든 직원 정보를 추가합니다.

Private Sub cmdListEmployees_Click()
   Dim emp As Employee
   lstEmployees.Clear
   For Each emp In sbMain.Employees
      lstEmployees.AddItem emp.ID & ", " & emp.Name _
      & ", " & emp.Salary
   Next
End Sub

cmdEmployeeDelete_Click 이벤트는 Collection 개체의 Remove 메서드를 사용하여 현재 ListBox 컨트롤에 선택된 컬렉션 구성원을 삭제합니다.

Private Sub cmdEmployeeDelete_Click()
   ' 선택된 직원이 있는지 확인합니다.
   If lstEmployees.ListIndex > -1 Then
      ' 처음 여섯 문자가 ID입니다.
      sbMain.Employees.Remove _
      Left(lstEmployees.Text, 6)
   End If
   ' 목록 새로 고침 단추를 누릅니다.
   cmdListEmployees.Value = True
End Sub

다음 코드를 오류 단추에 추가합니다.

Private Sub cmdTrouble_Click()
   ' 뭐라고 말씀해보세요?
   sbMain.Employees.Add Me
End Sub

cmdClose_Click 이벤트는 응용 프로그램을 종료합니다. 개체를 사용하는 프로젝트를 닫을 때 클래스 모듈의 Terminate 이벤트 프로시저가 실행되는지 확인하기 위해 모든 폼을 지우면 됩니다. 이와 반대로 End 문을 사용하면 Terminate 이벤트를 실행하지 않고 프로그램을 갑자기 중지합니다.

Private Sub cmdClose_Click()
   Unload Me
End Sub

예제에서 직원을 추가하려면 응용 프로그램을 실행하고 두 입력란에 값을 입력한 다음 추가 단추를 선택합니다. 직원을 몇 명 추가한 다음 삭제와 목록 단추로 실험해 봅니다.

초가집의 견고함

이 간단한 구현은 별로 견고하지 않습니다. Employees 속성이 공용 Collection 개체이기 때문에 프로그램의 아무 곳에서나 우연히 액세스할 수 있습니다. 게다가 Collection 개체의 Add 메서드는 아무런 형식 확인도 수행하지 않습니다. 예를 들어 오류 단추 Click 이벤트의 코드는 폼에 대한 개체 참조를 직원 컬렉션에 삽입해 버립니다.

오류 단추를 누르고 발생한 오류가 없는지 주의합니다. 이제 목록 새로 고침 단추를 누릅니다. For Each ... Next 루프에서 예기치 않은 개체 유형을 만나면 13번 오류(형식이 일치하지 않습니다.)가 발생합니다.

다음은 공용 Collection 개체와 함께 개체 모델을 만들 때 나타나는 오류 종류를 보여주는 예제입니다. 개체는 프로젝트의 아무 곳에서나 추가할 수 있고 알맞게 초기화된다는 보장이 없습니다. 프로그래머가 직원을 추가하기 위해 코드 사본을 만든 후 나중에 원본 코드가 변경될 경우 생기는 오류는 찾아내기가 매우 어렵습니다.

추가 정보   이 항목의 예제는 "전용 컬렉션 예제: 나무집"에서 계속됩니다.

 

전용 컬렉션 예제: 나무집

이 항목은 "공용 컬렉션 예제: 초가집"의 코드 예제가 계속되는 것입니다. 이 항목을 시작하기 전에 이전 항목을 읽으면 도움이 될 것입니다.

Employee 개체를 SmallBusiness 개체와 좀 더 견고하게 연결하는 방법은 Collection 개체를 전용으로 설정하는 것입니다. 다음 예제에서는 폼과 "전용 Collection" 예제의 코드 대부분을 다시 사용합니다.

Employee 클래스 모듈은 변경되지 않습니다. 그러나 SmallBusiness 클래스 모듈은 완성된 디자인 변경을 얻습니다. 공용 Collection 개체 선언을 다음 선언으로 바꾸고 다음 단락에 설명된 Sub Function 프로시저를 추가합니다.

Option Explicit
Private mcolEmployees As New Collection

직원을 추가하는 코드는 대부분의 작업을 수행합니다. 이전 예제의 cmdEmployeeAdd_Click 이벤트 프로시저 밖의 점선 사이 코드 블록을 사용할 수 있습니다.

중요한 변경 사항은 mcolEmployees Private이기 때문에 Collection 개체의 Add 메서드를 더 이상 프로그램의 모듈에서 호출할 수 없다는 것입니다. 새 개체를 올바르게 초기화하는 EmployeeAdd 메서드를 사용하는 Employee 개체만 추가할 수 있습니다.

' SmallBusiness 클래스의 메서드
Public Function EmployeeAdd(ByVal Name As String, _
ByVal Salary As Double) As Employee
   ' - - - - - - - - - - - - - - - -
   Dim empNew As New Employee
   Static intEmpNum As Integer
   ' With를 사용하여 코드를 더 빠르고 간결하게
   ' 만듭니다(.ID empNew.ID).
   With empNew
      ' 새 직원의 고유 ID를 만듭니다.
      intEmpNum = intEmpNum + 1
      .ID = "E" & Format$(intEmpNum, "00000")
      .Name = Name
      .Salary = Salary
      ' ID 속성을 키로 사용하여 컬렉션에
      ' Employee 개체 참조를 추가합니다.
      ' - - - - - - - - - - - - - - - -
      mcolEmployees.Add empNew, .ID
   End With
   '  Employee에 참조를 반환합니다.
   Set EmployeeAdd = empNew
End Function

EmployeeAdd 메서드는 새로 추가된 Employee 개체에 대한 참조를 반환합니다. 개체를 작성하자 마자 개체로 무슨 작업이든 수행할 것이므로 좋은 연습이 될 것입니다.

EmployeeCount, EmployeeDelete, Employees 등의 메서드는 Collection 개체의 해당 메서드에 위임합니다. 위임은 Collection 개체에서 모든 작업을 수행함을 의미합니다.

' SmallBusiness 클래스의 메서드
Public Function EmployeeCount() As Long
   EmployeeCount = mcolEmployees.Count
End Function
 
Public Sub EmployeeDelete(ByVal Index As Variant)
   mcolEmployees.Remove Index
End Sub
 
Public Function Employees(ByVal Index As Variant) _
As Employee
   Set Employees = mcolEmployees.Item(Index)
End Function

메모   이 메서드에 기능을 추가할 수 있습니다. 예를 들어 인덱스가 유효하지 않을 때 사용자 오류를 발생시킬 수 있습니다.

마지막 메서드는 Trouble입니다. 이 메서드는 초기화되지 않은 Employee 개체를 컬렉션에 추가하려고 합니다. 어떤 일이 일어날 지 짐작할 수 있습니까?

' SmallBusiness 클래스의 메서드
Public Sub Trouble()
   Dim x As New Employee
   mcolEmployees.Add x
End Sub

폼으로 변경

폼 모듈을 약간 변경해야 합니다. 이전 예제에 사용된 같은 모듈 수준의 선언을 사용할 수 있고 닫기 단추의 Click 이벤트가 같지만 나머지 이벤트 프로시저가 변경되었습니다. 삭제 단추와 직원 목록(List Employees) 단추 코드가 더 작지만 의미 있는 방법으로 변경된 반면 추가 단추 코드는 더 짧아집니다.

Private Sub cmdEmployeeAdd_Click()
   sbMain.EmployeeAdd txtName.Text, txtSalary.Text
   txtName.Text = ""
   txtSalary.Text = ""
   cmdListEmployees.Value = True
End Sub
 
Private Sub cmdEmployeeDelete_Click()
   ' 선택된 직원이 있는지 확인합니다.
   If lstEmployees.ListIndex > -1 Then
      ' 처음 여섯 문자는 ID입니다.
      sbMain.EmployeeDelete Left(lstEmployees.Text, 6)
   End If
   cmdListEmployees.Value = True
End Sub
 
Private Sub cmdListEmployees_Click()
   Dim lngCt As Long
   lstEmployees.Clear
   For lngCt = 1 To sbMain.EmployeeCount
      With sbMain.Employees(lngCt)
         lstEmployees.AddItem .ID & ", " & .Name _
         & ", " & .Salary
      End With
   Next
End Sub

cmdListEmployees_Click에서 이 추가 코드가 전부가 아닙니다. 불행하게도 Collection 개체는 현재 Private로 선언되었기 때문에 견고함을 얻기 위해 컬렉션의 항목을 통해 반복하는데 For Each ... Next를 사용할 수 있는 능력을 포기했습니다. 다음을 코드로 작성하면 오류가 일어납니다.

' Employees가 사실은 컬렉션이 아니기 때문에
' 작동하지 않습니다.
For Each emp In sbMain.Employees

다행히도 EmployeeCount 메서드는 반복 계산 영역을 구분하는데 사용할 수 있습니다.

오류 단추도 약간 변경되지만 여전히 잘 작동합니다.

Private Sub cmdTrouble_Click()
   sbMain.Trouble
End Sub

프로젝트를 실행하고 추가, 삭제, 목록 새로 고침 등의 단추로 실험해 보십시오. 모든 것이 전과 같이 작동할 것입니다.

오류 단추를 누르면 다시 한번 오류가 생깁니다. 그러나 목록 새로 고침 단추를 누르면 초기화되지 않은 Employee 개체에 컬렉션이 추가되어 있는 것을 확인할 수 있습니다.

어떻게 그럴 수 있을까요? Collection 개체를 전용으로 설정함으로써 SmallBusiness 개체 외부에 있는 프로그램의 모든 코드로부터 보호하지만 코드 내부로부터는 보호하지 않습니다. 다량의 코드가 있는 SmallBusiness 개체는 크고 복잡할 수 있습니다. 예를 들어 CustomerAdd, ProductAdd 등과 같은 메서드가 있을 것입니다.

전용 변수는 클래스 모듈을 통해 보이기 때문에 코드 작성 오류나 EmployeeAdd 메서드의 사본 작성은 여전히 컬렉션에 삽입되고 있는 잘못된 데이터(심지어 유효하지 않은 개체)로 끝날 수 있습니다.

추가 정보   이 예제는 "사용자 컬렉션 클래스 작성: 벽돌집"에서 계속됩니다.

 

사용자 컬렉션 클래스 작성: 벽돌집

이 항목은 "공용 컬렉션 예제: 초가집" "전용 컬렉션 예제: 나무집"의 코드 예제가 계속되는 것입니다. 이 항목을 시작하기 전에 이전 항목을 읽으면 도움이 될 것입니다.

가장 견고하게 컬렉션을 구현하는 방법은 클래스 모듈로 만드는 것입니다. 이전 예제와 대조를 이루어 개체 작성을 위해 모든 코드를 컬렉션 클래스로 이동하면 좋은 개체 디자인 원칙을 따르게 됩니다.

다음 예제에서는 같은 폼과 이전 예제와 같은 Employee 클래스 모듈을 사용합니다. 새 클래스 모듈을 삽입하고 Name 속성을 "Employees"로 설정한 후 다음 선언과 코드를 새 클래스 모듈에 삽입합니다.

Option Explicit
Private mcolEmployees As New Collection

Employees 클래스의 Add, Count, Delete 메서드는 특히 기존 SmallBusiness 클래스의 메서드와 같습니다. SmallBusiness 클래스 모듈에서 간단히 삭제하고 Employees 클래스 모듈에 붙여넣고 이름을 변경할 수 있습니다.

더 이상 CustomerAdd EmployeeAdd를 구별할 필요가 없기 때문에 이름을 변경할 수 있습니다. 구현하는 각 컬렉션 클래스에는 고유의 Add 메서드가 있습니다.

' Employees 컬렉션 클래스의 메서드
Public Function Add(ByVal Name As String, _
ByVal Salary As Double) As Employee
   Dim empNew As New Employee
   Static intEmpNum As Integer
   ' With를 사용하여 코드를 더 빠르고 간결하게
   ' 만듭니다(.ID empNew.ID).
   With empNew
      ' 새 직원의 고유 ID를 만듭니다.
      intEmpNum = intEmpNum + 1
      .ID = "E" & Format$(intEmpNum, "00000")
      .Name = Name
      .Salary = Salary
      ' ID 속성을 키로 사용하여 컬렉션에
      ' Employee 개체 참조를 추가합니다.
      mcolEmployees.Add empNew, .ID
   End With
   '  Employee에 참조를 반환합니다.
   Set Add = empNew
End Function
 
Public Function Count() As Long
   Count = mcolEmployees.Count
End Function
 
Public Sub Delete(ByVal Index As Variant)
   mcolEmployees.Remove Index
End Sub

SmallBusiness 개체의 Employees 메서드는 컬렉션 클래스의 Item 메서드가 되고 인덱스나 키로 구성원을 검색하기 위해 Collection 개체에 계속 위임합니다.

' Employees 컬렉션 클래스의 메서드
Public Function Item(ByVal Index As Variant) _
As Employee
   Set Item = mcolEmployees.Item(Index)
End Function

여기 추가할 수 있는 좋은 방법이 있습니다. Item Employees 클래스의 기본 메서드로 설정함으로써 Collection 개체에서 하던 대로 Employees("E00001")를 코딩할 수 있게 됩니다.

Item을 기본 속성으로 설정하려면

  1. [도구] 메뉴에서 [프로시저 특성]을 눌러 [프로시저 특성] 대화 상자를 엽니다. [이름] 상자에서 Item 메서드를 선택합니다.

     
  2. [고급]을 눌러 고급 기능을 나타냅니다. [프로시저 ID] 상자에서 [(기본값)]을 눌러 Item 메서드를 기본값으로 설정한 다음 [확인]을 누릅니다.

메모   클래스는 기본 구성원(속성이나 메서드)을 하나만 가질 수 있습니다.

For Each … Next 사용

For Each … Next를 견고하게 다시 사용할 수 있습니다. 다음 메서드를 추가함으로써 다시 Collection 개체에 모든 작업을 위임할 수 있습니다.

' NewEnum은 컬렉션 열거자의
' Iunknown 인터페이스를 반환해야 합니다.
Public Function NewEnum() As IUnknown
   Set NewEnum = mcolEmployees.[_NewEnum]
End Function

Collection 개체에 위임하고 있는 중요한 것은 그 열거자입니다. 열거자는 컬렉션에서 항목을 통해 반복하는 방법을 아는 작은 개체입니다. Visual Basic으로 열거자 개체를 작성할 수 없지만 Employees 클래스는 Collection 개체에 기초하기 때문에 Collection 개체의 항목 열거 방법을 원래 알고 있는 Collection 개체의 열거자를 반환할 수 있습니다.

Collection 개체의 _NewEnum 메서드 주위 대괄호는 메서드 이름 앞에 오는 밑줄 때문에 필요합니다. 앞에 오는 이 밑줄은 메서드가 형식 라이브러리에 숨겨져 있음을 나타내는 규정입니다. 메서드 _NewEnum의 이름을 지정할 수는 없지만 형식 라이브러리에 숨기고 For Each … Next에 필요한 프로시저 ID를 지정할 수는 있습니다.

NewEnum 메서드를 숨기고 필요한 프로시저 ID를 제공하려면

  1. [도구] 메뉴에서 [프로시저 특성]을 눌러 [프로시저 특성] 대화 상자를 엽니다. [이름] 상자에서 NewEnum 메서드를 선택합니다.

     
  2. [고급]을 눌러 고급 기능을 나타냅니다. [구성원 숨기기]를 눌러 형식 라이브러리에서 NewEnum를 숨깁니다.

     
  3. [프로시저 ID] 상자에 4를 입력하여 For Each Next에 필요한 프로시저 ID NewEnum에 제공한 다음 [확인]을 누릅니다.

중요   컬렉션 클래스에서 For Each … Next와 함께 작업하려면 숨겨진 NewEnum 메서드에 적절한 프로시저 ID를 제공해야 합니다.

얼마 남지 않은 SmallBusiness 클래스

SmallBusiness 클래스에는 현재 코드가 별로 없을 것입니다. Collection 개체와 삭제한 모든 메서드를 대체할 새 선언과 읽기 전용 속성이 있습니다.

Option Explicit
Private mEmployees As New Employees
 
Public Property Get Employees() As Employees
   Set Employees = mEmployees
End If

이것은 설명해야 합니다. Property Get를 생략한 채 단지 Public Employees As New Employees로 선언했을 경우입니다.

모두 실수하지 않으면 모든 것이 잘 진행되겠지만 실수로 Set sbMain.Employees = Nothing을 코딩했을 경우 어떻게 될까요? Employees 컬렉션이 소멸될 것입니다. Employees를 읽기 전용 속성으로 설정하면 그럴 가능성을 피할 수 있습니다.

폼으로 변경

폼 모듈에 대한 코드는 앞의 예제와 매우 비슷합니다. 같은 모듈 수준의 선언을 사용할 수 있고 닫기 단추의 Click 이벤트가 같습니다.

대부분의 이벤트 프로시저에서 유일하게 변경되는 것은 SmallBusiness 클래스의 기존 메서드를 Employees 컬렉션 개체의 새 메서드로 대체하는 것뿐입니다.

Private Sub cmdEmployeeAdd_Click()
   sbMain.Employees.Add txtName.Text, txtSalary.Text
   txtName.Text = ""
   txtSalary.Text = ""
   cmdListEmployees.Value = True
End Sub
 
Private Sub cmdEmployeeDelete_Click()
   ' 선택된 직원이 있는지 확인합니다.
   If lstEmployees.ListIndex > -1 Then
      ' 처음 여섯 문자가 ID입니다.
      sbMain.Employees.Delete _
      Left(lstEmployees.Text, 6)
   End If
   cmdListEmployees.Value = True
End Sub
 
Private Sub cmdListEmployees_Click()
   Dim emp As Employee
   lstEmployees.Clear
   For Each emp In sbMain.Employees
      lstEmployees.AddItem emp.ID & ", " & emp.Name _
      & ", " & emp.Salary
   Next
End Sub

For Each … Next를 다시 사용하여 직원을 나열할 수 있습니다.

프로젝트를 실행하고 모두 제대로 작동되는지 확인합니다. 여기서는 캡슐화에서 문제를 해결했기 때문에 오류 단추에 해당되는 코드가 없습니다.

추가 정보   컬렉션에서 배경은 "Visual Basic Collection 개체" "Visual Basic 컬렉션"을 참조하십시오.

Professional Enterprise Edition에 포함된 클래스 작성기 유틸리티로 컬렉션 클래스를 작성할 수 있습니다.

초가집, 나무집, 벽돌집의 예제 학습은 "우수한 개체 지향 디자인으로 얻는 이익"에 요약되어 있습니다.

 

우수한 개체 지향 디자인으로 얻는 이익

이 항목은 "공용 컬렉션 예제: 초가집"에서 시작하고 "전용 컬렉션 예제: 나무집" "사용자 컬렉션 클래스 작성: 벽돌집"에서 계속된 코드 예제의 결과를 요약한 것입니다. 이 항목을 시작하기 전에 이전 항목을 읽으면 도움이 될 것입니다.

Employees 컬렉션 클래스를 작성하면 매우 완벽한 모듈적 코드 작성 형식이 됩니다. 모든 컬렉션 코드는 SmallBusiness 클래스 모듈의 크기를 줄이는 컬렉션 클래스(캡슐화)에 있습니다. Employee 개체의 컬렉션이 개체 계층의 여러 위치에 나타날 경우 컬렉션 클래스를 재사용하려면 코드가 중복되지 않아야 합니다.

Collection 클래스 향상

컬렉션 클래스의 메서드와 속성을 추가로 구현할 수 있습니다. 예를 들어 Copy Move 메서드 또는 SmallBusiness 개체 참조가 있는 읽기 전용 Parent 속성을 구현할 수 있습니다.

또한 이벤트를 추가할 수도 있습니다. 예를 들어 Add Remove 메서드에서 컬렉션의 항목 수를 변경할 때마다 CountChanged 이벤트를 로드할 수 있습니다.

견고함, 견고함, 견고함

항상 가장 견고한 방법으로 컬렉션을 구현할 필요는 없습니다. 그러나 개체를 사용한 프로그래밍의 좋은 점 중 하나는 코드를 재사용할 수 있다는 것입니다. 원본 코드를 복사하는 것보다 개체를 재사용하는 것이 더 쉽고 견고하고 캡슐화된 코드를 사용하는 것이 더 안전합니다.

"진짜 견고한 코드를 기록하려면 정말로 나쁜 일이 일어날 것을 가정해야 한다"고 합니다.

Collection 클래스와 구성 요소 프로그램

Visual Basic Professional Enterprise Edition을 사용할 경우 작성한 개체를 회사의 다른 프로그래머가 사용할 수 있도록 프로젝트를 ActiveX 구성 요소로 바꿀 수 있습니다.

Collection 클래스 구현 단계

다음 목록에서는 컬렉션 클래스 작성에 필요한 단계를 요약한 것입니다.

  1. 프로젝트에 클래스 모듈을 추가하고 이름(보통 컬렉션 클래스가 포함할 개체 이름의 복수형)을 지정합니다. 이 장 앞부분의 "속성, 메서드, 이벤트 이름 짓기"를 참고하십시오.

     
  2. 속성과 메서드를 위임할 Collection 개체에 대한 참조를 포함하도록 전용 변수를 추가합니다.

     
  3. Class_Initialize 이벤트 프로시저에서 Collection 개체를 작성합니다. 필요할 때까지 이 개체 작성을 연기하려면 단계 2에서 전용 변수를 As New Collection으로 선언합니다. 이것은 Collection이 액세스될 때마다 소량의 오버헤드를 추가합니다.

     
  4. Count 속성과 Add, Item, Remove 메서드를 클래스 모듈에 추가합니다. 각 경우에 해당 구성원을 호출하여 전용 Collection에 위임합니다.

     
  5. Add 메서드를 구현할 때 한 종류의 개체만 허용하여 Collection 개체의 민감하지 않은 Add 메서드 동작을 무시할 수 있습니다. Add 메서드에서 개체의 작성과 초기화를 완전히 제어하도록 외부에서 작성된 개체를 컬렉션에 추가할 수도 있습니다.

     
  6. [프로시저 특성] 대화 상자에서 컬렉션 클래스의 Item 메서드를 기본값으로 설정합니다.

     
  7. 아래와 같이 NewEnum 메서드를 추가합니다. [프로시저 특성] 대화 상자에서 숨김으로 표시하고 For Each Next와 함께 작업할 수 있도록 프로시저 ID 4를 제공합니다.
8.     Public Function NewEnum() As IUnknown
9.     Set NewEnum = mcol.[_NewEnum]
10.   End Function

메모   위의 코드는 단계 2의 전용 변수 이름을 mcol로 가정합니다.

  1. 사용자 정의 속성, 메서드, 이벤트를 컬렉션 클래스에 추가합니다.

메모   Visual Basic Professional Enterprise Edition에 포함된 클래스 작성기 유틸리티로 컬렉션 클래스를 작성할 수 있습니다. 결과로 나온 원본 코드를 사용자 정의할 수 있습니다.

추가 정보   소프트웨어의 구성 요소에 대한 자세한 내용은 구성 요소 안내서ActiveX 구성 요소 작성을 참조하십시오.

 

개체 모델

일단 클래스 모듈을 작성하고 속성과 메서드를 지정하여 클래스를 정의했다면 해당 클래스에서 원하는 만큼 개체를 작성할 수 있습니다. 작성한 개체를 어떻게 추적하시겠습니까?

개체를 추적하는 가장 간단한 방법은 작성할 각 개체에 개체 변수를 선언하는 것입니다. 물론 작성할 수 있는 개체 수에는 제한이 있습니다.

이 장 앞부분의 "개체 배열 작성" "개체 컬렉션 작성"의 설명처럼 배열이나 컬렉션에서 다중 개체 참조를 유지할 수 있습니다.

처음에는 일반 변수에서 하는 것처럼 폼이나 표준 모듈에 개체 변수, 배열, 컬렉션 등을 추가할 것입니다. 그래도 클래스를 더 추가하면 사용 중인 개체간에 명백한 관계를 가진다는 것을 알게 될 것입니다.

포함 관계를 표현하는 개체 모델

개체 모델은 개체 기반 프로그램에 구조를 제공합니다. 프로그램에서 사용하는 개체간 관계를 정의함으로써 개체 모델은 프로그래밍을 쉽게 만드는 방법으로 개체를 관리합니다.

보통 개체 모델은 일부 개체가 다른 개체보다 "더 크거나" 더 중요함을 표현합니다. 이 때 다른 개체란 자신이 아닌 개체를 포함하거나 다른 개체로 구성된 것으로 생각할 수 있습니다.

예를 들어 SmallBusiness 개체를 프로그램 핵심으로 작성할 수 있습니다. Employee 개체와 Customer 개체처럼 SmallBusiness 개체에서 연관된 다른 개체 유형을 가질 수 있습니다. Product 개체를 포함할 수도 있습니다. 이 프로그램의 개체 모델은 그림 9.11에 나타나 있습니다.

그림 9.11   개체 모델

SmallBusiness, Employee, Customer, Product 등의 이름을 가진 네 개의 클래스 모듈을 정의하고 각각 적절한 속성과 메서드를 부여할 수 있지만 각각의 개체를 어떻게 연결하시겠습니까? 이런 목적에 사용할 수 있는 두 가지 도구가 있습니다. 다음 코드는 그림 9.11의 계층을 구현하는 한 방법을 나타냅니다.

' SmallBusiness 클래스 모듈의
' 선언 영역 코드
Public Name As String
Public Product As New Product
Public Employees As New Collection
Public Customers As New Collection

처음 Product 속성을 참조하면 As New로 선언되기 때문에 개체가 작성됩니다. 예를 들어 다음 코드는 SmallBusiness 개체의 Product 개체 이름과 가격을 작성하고 설정합니다.

' 표준 모듈 코드
Public sbMain As New SmallBusiness
Sub Main
   sbMain.Name = "Velociraptor Enterprises, Inc."
   ' 처음 Product 변수가 코드에서 사용될 때
   ' Product 개체가 작성됩니다.
   sbMain.Product.Name = "Inflatable Velociraptor"
   sbMain.Product.Price = 1.98
   .
   .   ' 기본 폼을 초기화하고 나타내는 코드
   .
End Sub

메모   공용 변수로 개체 속성을 구현하는 것은 조잡한 방법입니다. 코드에서 속성을 Nothing으로 설정하여 실수로 Product 개체를 없앨 수 있습니다. 다음 코드와 같이 개체 속성을 읽기 전용 속성으로 작성하는 것이 좋습니다.

' 견고한 개체 속성 코드
' 속성이 전용인 저장 공간입니다. 따라서 개체 외부에서
' Nothing으로 설정할 수 없습니다.
Private mProduct As New Product
 
Property Get Product() As Product
   ' 처음 이 속성이 호출되면
   ' mProduct Nothing을 포함하므로
   ' Visual Basic에서 Product 개체를 작성합니다.
   Set Product = mProduct
End If

일대다 개체 관계

개체 사이의 관계가 일대일이면 개체 속성은 바르게 작동합니다. 그러나 한 형식의 개체에 다른 형식의 여러 개체가 있는 경우가 자주 발생합니다. SmallBusiness 개체 모델에서 Employees 속성은 SmallBusiness 개체가 여러 Employee 개체를 포함할 수 있도록 Collection 개체로 구현됩니다. 다음 코드는 새 Employee 개체가 이 컬렉션에 추가되는 방법을 나타냅니다.

Public Function NewEmployee(Name, Salary, HireDate, _
ID) As Employee
   Dim empNew As New Employee
   empNew.Name = Name      ' 내부적인 개체 작성
   empNew.Salary = Salary
   empNew.HireDate = HireDate
   ' ID를 키로 사용하여 컬렉션에 추가합니다.
   sbMain.Employees.Add empNew, CStr(ID)
   '  Employee 참조를 반환합니다.
   Set NewEmployee = empNew
End Function

NewEmployee 함수는 SmallBusiness 개체에서 나타내는 사업체의 직원을 작성할 필요가 있을 때마다 호출됩니다. 기존 직원은 Employees 컬렉션에서 반복하여 언제라도 나열할 수 있습니다.

메모   다시 한번 말하지만 이것은 그다지 견고한 구현이 아닙니다. 더 좋은 연습은 사용자 컬렉션 클래스를 작성하고 읽기 전용 속성으로 드러내는 것입니다. 자세한 정보는 "사용자 고유 컬렉션 클래스 작성"을 참조하십시오.

   Visual Basic Professional Enterprise Edition에 포함된 클래스 작성기 유틸리티는 개체 모델을 구현하는데 필요한 만큼 코드를 작성할 수 있습니다. 클래스 작성기로 견고한 개체 속성과 컬렉션 클래스를 작성하고 모델을 쉽게 재배열할 수 있습니다.

Parent 속성

개체 참조가 있으면 그 개체 속성과 컬렉션을 사용하여 포함하는 개체에 도달할 수 있습니다. 또한 참조하는 개체를 포함하는 개체에 도달하도록 계층 구조의 윗부분으로 탐색하는데 매우 유용하게 사용할 수 있습니다.

위로 탐색하는 것은 보통 Parent 속성과 함께 수행됩니다. Parent 속성은 개체 컨테이너에 대한 참조를 반환합니다. 개체 모델 이동에 대한 자세한 내용은 "구성 요소를 사용한 프로그래밍" "개체 모델 탐색"을 참조하십시오.

이 장 앞부분의 "속성을 클래스에 추가"에서 Parent 속성의 예제를 참고할 수 있습니다.

   컬렉션에서 Parent 속성을 개체에 할당할 때 Collection 개체 참조를 사용하지 않는 것이 좋습니다. 실제 개체의 상위는 컬렉션이 있는 개체입니다. Parent 속성이 컬렉션을 가리키면 실제 상위를 얻기 위해 거짓으로 두 수준을 사용해야 합니다(, obj.Parent 대신 obj.Parent.Parent).

Parent 속성, 순환 참조, 개체 해체

Parent 속성의 가장 큰 문제점은 순환 참조를 작성한다는 것입니다. "더 큰" 개체에는 포함하는 개체 참조가 있고 포함된 개체에는 그림 9.12의 루프를 작성하는 Parent 속성을 통한 참조가 있습니다.

그림 9.12   순환 참조의 경우

이 그림에서 무엇이 잘못되었습니까? 개체 작업을 했을 때 개체를 제거하는 방법은 모든 개체 참조를 해제하는 것입니다. 이 항목의 앞부분처럼 SmallBusiness 개체 참조가 sbMain 변수에 있다고 가정하면 다음 코드를 기록할 수 있습니다.

Set sbMain = Nothing

불행하게도 여전히 SmallBusiness 개체 참조가 있습니다. 사실 각 Employee 개체의 Parent 속성은 SmallBusiness 개체 참조를 가지고 있기 때문에 많은 참조가 있을 수 있습니다.

SmallBusiness 개체의 Employees 컬렉션은 각 Employee 개체 참조를 가지고 있기 때문에 모든 개체가 소멸되지 않습니다.

TearDown 메서드

한 가지 해결책은 SmallBusiness 개체에 TearDown 메서드를 제공하는 것입니다. 이 메서드는 모든 SmallBusiness 개체의 개체 속성을 Nothing으로 설정할 수 있고 모든 Collection 개체(Employees, Customers) Nothing으로 설정할 수도 있습니다.

Collection 개체가 소멸되면 Visual Basic에서 가지고 있던 모든 개체 참조를 Nothing으로 설정합니다. Employee Customer 컬렉션에 포함된 Employee Customer 개체에 다른 참조가 없으면 해당 개체가 소멸됩니다.

물론, Employee 개체가 미세한 개체로 구성되었을 경우 상위와 같은 순환 참조 문제를 가집니다. 그런 경우에는 Employee 클래스에 TearDown 메서드를 제공해야 합니다. Employees Collection 개체를 Nothing으로 설정하는 대신 SmallBusiness 개체는 각 Employee 개체의 TearDown 메서드를 호출하면서 컬렉션을 통해 반복해야 합니다.

아직 끝나지 않았습니다

모든 개체가 소멸되지 않을 수 있습니다. SmallBusiness 개체나 포함하는 개체에 대한 참조를 여전히 가지고 있는 프로그램에 변수가 있을 경우 이 개체는 소멸되지 않습니다. 프로그램 정리 부분에서 모든 개체 변수를 Nothing으로 설정했는지 확인해야 합니다.

무엇이 일어나는지 알기 위해 개체에 일부 디버깅 코드를 추가할 수 있습니다. 예를 들어 다음 코드를 표준 모듈에 추가할 수 있습니다.

' 전역 디버그 컬렉션
Public gcolDebug As New Collection
 
' 각 개체에 고유 ID를 제공하는 전역 함수
Public Function DebugSerial() As Long
   Static lngSerial As Long
   lngSerial = lngSerial + 1
   DebugSerial = lngSerial
End Function

각 클래스 모듈에서 다음과 비슷하게 코드를 추가할 수 있습니다. 각 클래스는 "Product"가 나타나는 위치에 고유 이름을 제공합니다.

' 디버그 ID 저장 공간
Private mlngDebugID As Long
 
Property Get DebugID() As Long
   DebugID = mlngDebugID
End Property
 
Private Sub Class_Initialize()
   mlngDebugID = DebugSerial
   ' 전역 컬렉션에 문자열 항목을 추가합니다.
   gcolDebug.Add "Product Initialize; DebugID=" _
   & DebugID, CStr(DebugID)
End Sub
 
Private Sub Class_Terminate()
   ' 개체가 더이상 주위에 없는지 알기 위해
   ' 문자열 항목을 삭제합니다.
   gcolDebug.Remove CStr(DebugID)
End Sub

각 개체가 작성되면 전역 컬렉션에 문자열을 저장합니다. 개체가 소멸되면 문자열을 삭제합니다. 언제라도 소멸된 개체를 확인하도록 전역 컬렉션에서 반복합니다.

추가 정보   ProgWOb.vbg 예제 응용 프로그램에서 다양한 디버깅 기술을 설명합니다. Visual Basic Professional Edition Enterprise Edition을 사용하여 ActiveX 구성 요소를 작성할 때 개체 모델은 새 중요 사항 및 다양한 문제점들을 나타냅니다. 구성 요소 안내서ActiveX 구성 요소 작성에서 "구성 요소 디자인의 일반 원리"를 참조하십시오.

 

배열 대신 컬렉션 사용

개체로 작업을 할 때 컬렉션을 가장 많이 사용하지만 데이터 형식의 작업에서도 컬렉션을 사용할 수 있습니다. 어떤 경우에서는 항목을 컬렉션에 저장하는 것이 배열에 저장하는 것보다 더 효율적입니다.

작고 동적인 항목의 집합을 사용하고 있는 경우 컬렉션을 사용할 경우가 있습니다. 다음 코드 부분은 URL 주소의 목록을 저장하고 화면에 나타낼 수 있도록 컬렉션을 사용하는 방법을 보여줍니다.

' 모듈 수준 컬렉션
Public colURLHistory As New Collection
 
' 지정된 URL 주소를 컬렉션에 
' 추가하기 위한 코드
Private Sub SaveURLHistory(URLAddress As String)
   colURLHistory.Add URLAddress
End Sub
 
' 직접 실행 창에 URL 주소의 목록을 
' 나타내기 위한 코드
Private Sub PrintURLHistory()
   Dim URLAddress As Variant
   For Each URLAddress in colURLHistory
      Debug.Print URLAddress
   Next URLAddress
End Sub

추가 정보   컬렉션 사용에 대한 자세한 내용은 "개체를 사용한 프로그래밍" "사용자 고유 개체 프로그래밍"을 참조하십시오. 배열 사용에 대한 자세한 내용은 "프로그래밍 기초" "배열"을 참조하십시오.

 

Enum을 사용하여 상수 집합 작업

Enum(Enumerations)은 관련 상수 집합으로 작업하거나 상수값을 이름과 관련지을 때 편리한 방법을 제공합니다. 한 주의 요일과 관련하여 정수 상수 집합의 Enum를 선언하고 코드에서 정수값 대신 요일 이름을 사용할 수 있습니다.

표준 모듈이나 공용 클래스 모듈의 선언 영역에서 Enum 문을 사용하여 Enum 형식을 선언하고 Enum을 만들 수 있습니다. Enum 형식은 적절한 키워드를 가진 Private 또는 Public으로 선언할 수 있습니다. 사용 예는 아래와 같습니다.

Private Enum MyEnum

또는

Public Enum MyEnum

기본 설정으로 Enum의 첫째 상수가 0의 값으로 초기화되고 다음 상수는 이전 상수보다 1씩 큰 값으로 초기화됩니다. 예를 들어 아래 Days Enum 0의 값을 가진 Sunday 상수, 1값을 가진 Monday 상수, 2값을 가진 Tuesday 상수 등을 포함하고 있습니다.

Public Enum Days
   Sunday
   Monday
   Tuesday
   Wednesday
   Thursday
   Friday
   Saturday
End Enum

   Visual Basic은 요일에 대한 상수를 가지고 있는 미리 작성된 vbDayOfWeek Enum을 제공합니다. Enum의 미리 정의된 상수를 보려면 코드 창에서 vbDayOfWeek를 입력하고 마침표를 찍습니다. Visual Basic은 자동으로 Enum 상수 목록을 나타냅니다.

대입문을 사용하여 Enum으로 상수값을 명시적으로 지정할 수 있습니다. 음수를 포함하여 Long형 정수값도 지정할 수 있습니다. 예를 들어 오류 상태를 나타내기 위해 0보다 작은 상수를 사용할 수도 있습니다.

아래 Enum에서 Invalid 상수에 –1의 값을 명시적으로 지정하고 Sunday 상수에는 0값을 지정하였습니다. 그것이 Enum의 첫째 상수이기 때문에 Saturday 0값으로 초기화됩니다. Monday의 값은 1(Sunday보다 1이 큰 값), Tuesday의 값은 2입니다.

Public Enum WorkDays
   Saturday
   Sunday = 0
   Monday
   Tuesday
   Wednesday
   Thursday
   Friday
   Invalid = -1
End Enum

메모   Visual Basic Enum 상수값을 Long 형식의 정수로 취급합니다. Enum 상수에 실수값을 지정할 경우 Visual Basic Long 정수로 반올림합니다.

Enum에서 관련 있는 상수의 집합을 정리하여 다른 구문에서 같은 상수 이름들을 사용할 수 있습니다. Days WorkDays Enum의 요일 상수에 동일한 이름을 사용할 수 있습니다.

개별 상수를 사용할 때 불분명한 사용을 피하려면 Enum의 상수 이름을 앞에 붙여줍니다. 아래 코드는 직접 실행 창에 서로 다른 값을 나타내는 Days WorkDays Enum Saturday 상수를 사용합니다.

Debug.Print "Days.Saturday = " & Days.Saturday
Debug.Print "WorkDays.Saturday = " & WorkDays.Saturday

둘째 Enum의 상수값을 지정할 때 한 Enum의 상수값을 사용할 수 있습니다. 예를 들어 아래 WorkDays Enum의 선언은 위의 선언과 동등합니다.

Public Enum WorkDays
   Sunday = 0
   Monday
   Tuesday
   Wednesday
   Thursday
   Friday
   Saturday = Days.Saturday - 6
   Invalid = -1
End Enum

Enum을 선언하고 난 후에 그 형식의 변수를 선언할 수 있습니다. 그런 다음 Enum의 상수값을 저장하는데 그 변수를 사용합니다. 아래 코드는 WorkDays Enum 상수와 연관된 정수값을 저장하기 위해 WorkDays 형식의 변수를 사용합니다.

Dim MyDay As WorkDays
MyDay = Saturday         ' Saturday 0이 됩니다.
If MyDay < Monday Then   ' Monday 1이 됩니다.
                     ' 따라서 Visual Basic은 아래와 같은
                     ' 메시지 상자를 보여줍니다.
   MsgBox "주말입니다. 근무일이 아닙니다."
End If

코드 창의 예제에서 코드 둘째 줄을 입력할 때 Visual Basic은 자동으로 구성원 자동 목록에 있는 WorkDays Enum 상수를 나타냅니다.

그림 8.7   Visual Basic Enum 상수를 자동으로 나타냅니다

상수 Sunday 0이 되기 때문에 예제의 둘째 줄의 "Saturday" "Sunday"로 대체하면 Visual Basic은 메시지 상자를 나타내기도 합니다.

MyDay = Sunday         ' Sunday 0이 됩니다.

메모   Enum 형식으로 선언된 변수에 Enum 상수값을 정상적으로 지정하였더라도 그 변수에 Long 형식의 정수를 지정할 수 있습니다. Enum 상수와 관계가 없는 변수에 값을 지정하더라도 Visual Basic에는 오류가 발생하지 않습니다.

추가 정보   "Enum "을 참조하십시오. 또는 Professional edition Enterprise edition "ActiveX 구성 요소 작성"에서 "명명된 상수를 구성 요소에 제공"을 참조하십시오.

 


'language > VB6' 카테고리의 다른 글

The Sequence of Events  (0) 2010.06.18
블로그 이미지

란마12

,

The Sequence of Events

Driven by Events
Visual Basic is an "event driven" language. What this means is that your coding
will be performed in the sequence that the events occur, as opposed to the
order in which it appears in your form module. It is therefore very important,
if you want to create quality applications, to know which events will happen
when.


Start Up and Shut Down Events
The most logical place to start would be the 6 events that occur when the form
loads up and the 3 events that occur when the form shuts down.
They are, in this sequence:

Start Up:
Form_Initialize
Form_Load
Form_Resize
Form_Activate
Form_GotFocus
Form_Paint
Shut Down:
Form_QueryUnload
Form_Unload
Form_Terminate



This is what happens most of the time. You know, with a normal form in a little
demo program. In many real life situations, things can be very different. Read
on.


The Form_Initialize event always happens, and it always happens first. After
that, unless something dreadful happens, like an END statement or a fatal error
(you cannot Unload at this stage), the next event will be triggered.


That event is always the Form_Load event, without exception. From this point on
you can unload the form, or do all sorts of other stuff to end the program - so
the following 4 events rely on this not happening. Let's assume that you don't
end the program at this stage.


The Form_Resize event will always happen, whether the form is visible,
minimized or maximized, it will happen. The form receives its physical
dimensions even if it is hidden - so after being loaded, it resizes.


The Form_Activate event is a little more picky. It will only happen if the form
becomes the active form in the application. If for example you have loaded
another form in this form's Load event, and that other form is the active one,
this form's Activate event will not happen until it becomes the activated form.
The event will also not be triggered if this form starts as minimized. Only
when the form is restored or maximized, and becomes active, does this event
occur. This event can of course happen any number of times in the lifetime of a
form. While your user jumps from form to form, the Form_Deactivate and
Form_Activate events occur, telling you whether your user is coming or going.


The Form_GotFocus event, like the Activate event, relies on the form being
visible. But even more picky - only when the form gets the focus. This hardly
ever happens, except if the form does not contain any other controls that can
get the focus. For example, if your form contains a command button, the button
gets the focus after the form loads up, and the Form_Gotfocus does not occur.
If you can think of a good use for this event, please let me know.


The Form_Paint event occurs whenever the form, or any part of it, gets redrawn.
So obviously it happens when the form becomes visible after loading up. It also
happens when you drag something over the form - as each part of the form
becomes exposed and is redrawn, the Form_Paint event occurs. Also, if you are
making a form bigger by resizing it, the parts of the form that becomes visible
as you drag it, triggers the Form_Paint event, sometimes several times a second.


Let's look at the three shut down events:
The Form_QueryUnload event is the first indication that the form wants to shut
down. This is triggered by using the VB "Unload" command, or by clicking the
little cross in the top right corner of the form, by pressing Alt-F4 to close
the form, when Windows is shutting down, when the form's MDIForm parent is
shutting down (if applicable), and so on. The only time your form will
disappear without triggering this event is if you pull the plug on your
computer, or a fatal error occurs in Windows or in your application, or if you
use the END statement. This event is commonly used for asking the user "Do you
want to save changes?" and then saving the changes if the user so wishes. You
can cancel the unloading of the form by setting the Cancel parameter to True
(or any non-zero value).


The Form_Unload event confuses a lot of people who all want to know what the
difference is between QueryUnload and Unload. Well, the main difference is that
the QueryUnload event is for asking the user "Do you want to save" and "Are you
sure?", and then tidying up user stuff, like saving changes etc. The Unload
event happens after the user has said "Yeah sure, go ahead" and you have saved
all the changes. Now you are tidying up the system stuff - making sure all
files are closed and all objects are set to nothing. Also making sure that all
other forms are also unloaded. You can Cancel the unloading of the form at this
point as well, although this is seldom done and is not recommended unless doing
so can save the user from potential loss of data.


The Form_Terminate event also happens.




The Other Events
Although starting up and shutting down are by far the most common event
sequences, there are several other very important combinations of events to
take note of, as listed delicately below:

Clicking on a Form:
Form_MouseDown
Form_MouseUp
Form_Click
Double Clicking on a Form:
Form_MouseDown
Form_MouseUp
Form_Click
Form_MouseMove
Form_DblClick
Form_MouseUp
Pressing a Key on a Form
Note that the active control of the form normally receives the Key events,
unless there is no active control, or if the KeyPreview property of the form is
True.
Form_KeyDown
Form_KeyPress
Form_KeyUp
Minimizing then Restoring a Form:
Form_Resize (when minimizing)
Form_Resize (When Restoring. No events occur while the form is minimized.)
Form_Paint
Calling a Message box from a Form
The form does not respond to ANY events while the message box is displayed.
Form_Paint (this only happens if a part of the form becomes visible when the
message box is closed.)
Dragging something onto a Form
If the Form's OLEDropMode property is set to "None", nothing happens, otherwise
the events below occur.
Form_OLEDragOver (occurs continuously while dragging.)
Form_MouseMove (once, when dropping)
Form_OLEDragDrop (once, when dropping)

ThisArticle_Terminate Event
There are of course many more combinations of events that you and your form may
experience, but I believe that the most common ones had been covered here. If
this article triggers enough question and request events, I might just write a
follow up.

'language > VB6' 카테고리의 다른 글

우수한 개체 지향 디자인으로 얻는 이익  (0) 2010.06.18
블로그 이미지

란마12

,
보통 서로 다른 장, 단점을 갖고 있는 JAVA와 C++, 이 두 언어를 결합할 수만 있으면 큰 시너지를 발휘할 수 있다고 생각한다. 뭐, 어느 정도의 범위에서는 그렇긴 하지만...그러나 왠만하면, 정말 어쩔 수 없는 경우를 제외하고는 이 JNI 기술은 활용하지 않는 것이 최상이라고 난 생각한다. -_-;;

이제 마소에서 두 언어를 결합하는 방법을 전격 공개한다. 먼저 자바의 JNI를 이용해 C로 작성된 Win32 DLL을 호출하는 방법을 알아볼 텐데, 자바 코드에서 만든 데이터를 C 코드로 전달하거나 혹은 C 코드에서 생성된 데이터를 자바로 전달하는 방법, 한글 문제를 해결하는 방법 등도 포함돼 있다. 더불어 C/C++에서 자바 VM을 이용하는 방법도 함께 소개한다.

자바 네이티브 메쏘드(Java Native method, 이하 JNI)는 다른 언어로 작성된 코드를 자바에서 호출하도록 만들어진 규약이다. 현재는 C/C++에 대한 호출만을 정확하게 지원한다. 어떻게 보면 JNI는 자바가 만들어진 철학과 정반대되는 것이다. 네이티브 함수가 플랫폼에 종속적이기 때문이다. 자바의 가장 큰 장점 중의 하나로 내세우는 ‘플랫폼 독립적’이라는 부분을 해치는 JNI 규약이 만들어진 것은, 자바의 현실적인 문제들 때문이다.

먼저 속도 문제를 들 수 있다. JIT 컴파일러와 같은 기술로 자바 프로그램의 속도가 예전보다는 많이 빨라지고 있지만, 자바는 원칙적으로 바이트 코드를 인터프리트해 수행되기 때문에 아무리 자바 기술이 발전한다 해도, 네이티브 코드(플랫폼에 종속적인 기계어 코드)의 속도를 따라갈 수는 없다. 사실 자바의 처리 속도 문제는 자바가 안고 있는 가장 큰 단점이기도 하다. 따라서 아주 빠른 처리가 요구되는 대량의 계산 작업이나 실시간(real-time) 처리에는 자바를 이용하기 힘들다. - 이제 속도 문제는 신경을 안 써도 된다. 정말 속도가 중요시되는 전문 과학 분야를 제외하고는 JAVA의 처리 속도로도 거의 모든 문제를 해결할 수 있다.

JNI가 필요한 것은 단지 속도 문제만이 아니다. 기존에 작성된 프로그램이나 기존의 시스템(legacy)과의 연계 문제가 있으며, 더욱 중요한 이유는 플랫폼에 따라 다르게 제공되는 서비스를 이용할 수 있다는 점이다. 자바의 클래스 라이브러리는 방대하고 다양한 서비스를 제공하지만, 특정 플랫폼에서 제공하는 고유의 서비스의 기능을 모두 포함할 수는 없다. 특히, 특수한 목적으로 제작된 하드웨어를 자바에서 제어해야 할 필요가 있다고 한다면, 자바만으로 해결하기는 힘들다.

이 때문에 JNI가 필요하다. 지나치게 JNI에 의존하는 것은 자바가 갖고 있는 많은 장점들을 해치는 결과를 초래할 수 있지만, 특정 부분에 적절하게 사용한다면 JNI는 자바의 장점과 C/C++의 장점을 골고루 이용할 수 있는 길을 제공한다.

여기서 한 가지 궁금증이 생길 것이다. 그렇다면 과연 JNI를 어떤 부분에 사용해야 앞서 언급한 효과를 볼 수 있을까? JNI가 사용되면 좋은 곳을 정리해 보면 다음과 같다.

1. 속도 문제가 있는 계산 루틴
2. 자바에서 하드웨어 제어
3. 자바에서 지원되지 않은 특정 운영체제 서비스
4. 기존의 프로그램에서 자바가 제공하는 서비스를 이용

자바가 엔터프라이즈 솔루션에 많이 사용되는 것은 자바가 갖고 있는 많은 장점들이 대규모 시스템 구축에 적합하다는 강점을 인정받고 있는 셈이다. JNI는 그 자체가 중요한 솔루션이 될 수는 없지만, 기존의 코드나 기존의 시스템 혹은 이질적인 다른 시스템과의 인터페이스에 사용될 수 있는, 자바가 가진 좋은 기능 중의 하나이다.


★ 간단한 JNI 프로그래밍, HelloWorld

자바 프로그래밍을 한 번이라도 해 본 사람이라면, 다음과 같은 HelloWorld 프로그램을 작성해 보았을 것이다.

public class HelloWorld {

       public static void main(String arg[]) {

             System.out.println("Hello, World");

       }

}


이제 이 프로그램을 JNI 버전으로 다시 만들어 보는 것부터 시작해 보려 한다. 가장 간단한 JNI 호출 방법과 컴파일 방법을 익히기 위해서이다

1단계 : 네이티브 메쏘드를 가진 클래스 작성

두 개의 자바 프로그램을 작성하고, 컴파일해 NativeHello.class, HelloWorld.class 파일을 생성한다.

C:\jni>javac NativeHello.java
C:\jni>javac HelloWorld.java

NativeHello.java 프로그램은 C/C++로 작성된 Win32 네이티브 코드를 호출하기 위한 네이티브 메쏘드를 선언하는 부분이다.

public native void SayHelloWorld();

SayHelloWorld는 자바에서 호출하는 메쏘드 이름이 되는데, ‘native’ 키워드를 이용해 선언하며, 메쏘드의 몸체(body)는 없다. 몸체는 C/C++ 코드로 컴파일된 DLL에 만들어진다. HelloWorld.Java는 NativeHello 객체를 만들어 SayHello World() 메쏘드를 호출하는 간단한 형태의 프로그램이다

2단계 : C 헤더 파일 생성

javah 명령을 써서 다음과 같이 헤더 파일을 생성한다. javah 명령은 JDK에 포함돼 있는 명령으로 .class 파일을 읽어, 여기에 들어있는 네이티브 메쏘드를 판독하고, 이를 C 헤더 파일로 생성해 낸다.

C:\jni>javah -jni NativeHello

이 명령을 쓰려면 물론 JDK의 환경변수(특히 CLASSPATH 등)가 세팅돼 있어야 한다. 이렇게 하면 NativeHello.h라는 C 언어 헤더 파일이 생성된다. 그리고 옵션으로 jni를 주어야 한다. 물론 jni 옵션을 주지 않아도 헤더 파일이 생성되지만, JNI 스타일의 헤더가 아닐 것이기 때문이다. JDK 1.0 스타일의 헤더 파일을 생성하고자 한다면 -stubs 옵션을 준다.

생성된 헤더 파일인 NativeHello.h를 살펴보자. 이 파일은 다음과 같은 함수 선언을 포함하고 있다.

JNIEXPORT void JNICALL Java_NativeHello_SayHelloWorld(JNIEnv *, jobject);

이 파일의 첫 부분에 ‘DO NOT EDIT THIS FILE’이란 말이 있다. 실제로 이 헤더 파일은 수정하지 말아야 한다. 이 Java_Native Hello_SayHelloWorld 함수 선언은 JNIEnv struct 포인터와 jobject 등 두 개의 인수를 갖고 있다. 이 두 개의 인수는 모든 JNI 호출에 추가된다.

첫번째 인수인 JNIEnv 포인터는 자바 가상머신(Virtual Machine, 이하 VM) 영역에 대한 포
인터이고, 두번째 jobject는 C++의 this에 해당한다. 즉, 클래스를 참조하는데 사용된다.

생성된 헤더 파일에서 ‘Signature:’ 부분도 눈여겨볼 필요가 있다. 현재 ‘V’라는 글자가 있는데 이것은 리턴 타입이 void란 의미이다.

javah 사용과 관련해 지정할 수 있는 옵션들은 다음과 같은 것들이 있다.

-o : 출력될 파일의 이름을 지정한다.
-d : 출력 디렉토리를 지정한다.
-jni : JNI 스타일의 헤더를 생성하도록 한다.
-td : 작업용 임시 디렉토리를 지정한다.
-stubs : stub 파일을 생성한다(JDK 1.0 스타일).
-trace : stub 파일에 추적 정보를 덧붙인다.
-verbose : 작업에 대한 자세한 정보를 출력한다.
-classpath : 클래스 경로를 지정한다. 디렉토리는 ‘;’으로 분리한다.
-version : 빌드 버전을 출력한다.

3단계 : 네이티브 메쏘드의 몸체가 될 C 코드 작성

1. 비주얼 C++에서 Win32 Dynamic-Link Library 타입으로 Native Hello라는 이름의 프로젝트를 생성한다.
2. 위저드의 1/1 단계에서 ‘A Simple Projcet’를 선택한다.
3. javah로 생성된 헤더 파일(NativeHello.h)을 프로젝트 디렉토리로 옮기고, NativeHello
.cpp 파일을 <리스트 4>와 같이 수정한다. 여기서는 헤더에서 생성된 함수의 이름을 그대로
구현하는 것이다.
4. Prject / Settings 메뉴를 선택한 뒤, <화면 1>과 같이 다음 인클루드 디렉토리를 추가
한다. 이 jni.h 파일은 네이티브 메쏘드를 위해 JDK가 제공하는 헤더 파일이다.

C:\jdk1.2.2\include,C:\jdk1.2.2\include\win32

화면 1

화면 1


4단계 : 빌드하여 NativeHello.dll 파일을 생성하고 프로그램을 수행한다.

NativeHello.dll 파일을 NativeHello.class 파일이 있는 곳에 복사한다. 이렇게 하면 준비가 다 된 것이다. 이제 HelloWorld Java를 다음과 같이 수행한다.

C:\jni>java HelloWorld

<화면 2>는 이것의 수행된 결과이다. 간단한 메시지 박스가 나타난다. HelloWorld.java에서 NativeHello 클래스의 SayHelloWorld() 네이티브 메쏘드를 호출하는데, 이 네이티브 메쏘드의 몸체가 바로 NativeHello.cpp 파일의 Java_NativeHello_ SayHelloWorld() 함수가 되는 것이다.

이 함수는 간단히 MessageBox()란 Win32 API를 호출하는 내용으로 돼있다. DLL은 사실상 Win32의 실행 파일과 마찬가지로 모든 Win32 API를 사용할 수 있기 때문에 이론적으로는 JNI에서 모든 Win32 API를 이용할 수 있다.

화면 2

화면 2



★ 자바의 데이터 타입, C/C++의 데이터 타입

우리는 앞에서 JNI 프로그래밍의 전체적인 과정을 훑어보기 위해 가장 간단한 형태의 JNI 프로그램을 작성해 보았다. 사실 독자들이 가장 궁금해 할 내용은 아마도 자바 코드에서 만들어진 데이터를 C 코드로 전달하거나, 혹은 C 코드에서 생성된 데이터를 자바로 전달해주는 방법일 것이다. 일단 다음과 같이 String 타입의 객체를 인수로 전달하는 경우를 살펴보자.

public class NativeClass {

       public native void setString(String s);

       public native void setInt(int i);

       public native void setData(float f, double d);

       ...

}


이 코드를 컴파일한 후 javah로 C 헤더 파일을 생성하면 헤더에는 다음과 같은 내용의 함수
선언이 생성된다.

JNIEXPORT void JNICALL Java_NativeClass_setString(JNIEnv *, jobject, jstring);
JNIEXPORT void JNICALL Java_NativeClass_setInt(JNIEnv *, jobject, jint);
JNIEXPORT void JNICALL Java_NativeClass_setData(JNIEnv *, jobject, jfloat, jdouble);

생성된 함수 선언을 보면, 모든 함수의 인수가 두 개 이상인 것을 확인할 수 있다. 처음 두
개의 인수(JNIEnv *, jobject)는 모든 JNI 함수에 생긴다. 세번째 인수부터가 우리가 전달
하고자 하는 인수이다.

JNI에는 C 언어와의 데이터 타입의 차이를 극복하기 위해 헤더에 ‘j’로 시작하는 새로운 타입을 재정의했다. 이 데이터 타입의 크기가 C와 같을 수도 있지만, 경우에 따라서는 다를 수도 있다.

예를 들어 Win32 환경의 경우 jint는 int와 같은 32비트 정수이다. 하지만 jchar은 char 타입과 다르다. 자바의 char은 16비트 정수이며, C의 char 타입은 8비트 정수이다. 또한 일반적으로 비주얼 C++에서 사용하는 BOOL은 int 타입을 재정의한 것이다. 따라서 자바의 boolean에 해당하는 jboolean 타입과는 크기가 다르다. 참고로 자바에서 true, false에 해당하는 부울 값은 에 다음과 같이 정의돼 있다.

#define JNI_FALSE 0
#define JNI_TRUE 1

이런 차이점에 유의해서 코드를 작성해야 한다(<표 1>).

<표 1> JNI에서 사용하는 데이터 타입과 범위
Java
C(JNI)
비트수
C/C++(Win32)
boolean
jboolean
8 unsigned
unsigned char
byte
jbyte
8
unsigned char
char
jchar
16 unsigned
unsigned short
short
jshort
16
short
int
jint
32
int
long
jlong
64
_int64 (비주얼C++)
float
jfloat
32
float
double
jdouble
64
double
void
void
-
void


★ UTF-8 문자열 다루기

자바 코드와 C 코드 간에는 jstring 타입으로 문자열을 주고받는데, jstring은 객체이기 때문에 C에서 ‘char *’와 같은 형태로 바로 이용할 수는 없다. 때문에 이를 위해 몇 가지 함수들이 제공된다.

우리는 두 가지 문제만 해결할 수 있으면 충분하다. 하나는 자바 코드에서 넘어온 jstring 타입을 C의 ‘char *’로 바꾸어 처리하는 방법과 C 코드에서 생성한 문자열을 자바로 리턴하는 방법이다. 먼저 자바에서 생성된 String 타입(jstring)의 객체를 C 코드에서 다루는 방법을 살펴보자.

자바에서는 아스키(ASCII) 문자열 이외에 UTF-8, UTF-16과 같은 문자열 포맷이 지원된다. 이것은 7비트로만 나타낼 수 없는 문자들(한글, 한자 등), 2바이트 이상으로 표현되는 문자들을 지원하기 위한 포맷이다. UTF-8(Universal Character Set Transforma tion Format, 8bit)은 8비트 즉, 바이트의 나열로 표시된다.

영문 아스키 문자의 경우는 일반적인 C의 문자열과 다르지 않다. 0x0001∼0x007F 범위의 문자는 1바이트를 사용하며, 0x0080~0x07FF 범위의 문자를 표현할 때는 2바이트가 사용된다.

첫번째 바이트는 이진수 110으로 시작하며, 두번째 바이트는 10으로 시작한다. 따라서 연속
된 두 바이트를 다음과 같이 조합해 하나의 유니코드 문자를 만들게 된다.

((첫번째_바이트 & 0x1f)<<6) + (두번째_바이트 & 0x3f)

이와 비슷하게 0x0800∼0xFFFF 범위 사이의 문자 코드는 3개 바이트를 사용해 표현하며, 다음과 같이 연산해 하나의 유니코드 문자를 만들어 내게 된다. <표 2>는 UTF-8이 인코딩되는
방법을 보여주고 있다.

((첫번째_바이트 & 0xf)<<12)+((두번째_바이트 & 0x3f) <<6) + (세번째_바이트 & 0x3f)

<표 2> UTF-8 포맷의 문자열 인코딩 방식
유니코드(16진수)
인코딩된 바이트 수
UTF-8로 인코딩된 비트(2진수)
0x0001~0x007F
1
0xxxxxxx
0x0080~0x07FF
2
110xxxxx 10xxxxxx
0x0000
-
-
0x0800~0xFFFF
3
1110xxxx 10xxxxxx 10xxxxxx

자바 VM이 사용하는 UTF-8 포맷에 대해 주의할 점이 있다. 첫번째는 널(null, 0x0000) 문자에 대한 표현이다. 0x0000 문자는 1바이트가 아닌 두 바이트로 표시된다. 즉, 0x0000 문자를 2진수 비트로 표시해 보면,

11000000 10000000

의 두 바이트로 나열된다. 따라서 C에서처럼 ‘\0(2진수 000 00000)’으로 끝나는 문자열이 아니라는 점을 알 수 있다. 만일 여러분이 jstring을 strcpy()와 같은 함수에 직접 사용한다면, ‘\0’ 문자가 없기 때문에 분명 프로그램이 죽을 것이다. 반대로 C의 문자열을 그대로 jstring으로 넘긴다면 자바 VM이 죽는다. jstring을 잘못 사용하는 예를 들어보자.

JNIEXPORT void JNICALL Java_WinDialogBox_setTextField(JNIEnv *env, jobject obj, jstring str) {
    char strBuff[128];
    strcpy(strBuff, str) ; // C 모듈이 crash한다.
}

JNIEXPORT jstring JNICALL Java_WinDialogBox_getTextField(JNIEnv *env, jobject obj) {
    char strBuff[128] = "Hello Java VM";
    return strBuff; // VM이 crash한다.
}

JNI는 jstring과 C의 문자열 처리를 위해 GetStringUTFChars (), ReleaseStringUTFChars() 함수를 제공한다.

GetString UTFChars() 함수는 UTF-8 포맷의 문자열을 널로 끝나는 C 스타일의 문자열로 전환해 포인터를 리턴하는 함수이다.

반대로 Relea seStringUTFChars() 함수는 원상태로 복구하는 함수이다. 덧붙여 C 스타일의 문자열을 UTF-8 문자열로 만들려면, New StringUTF() 함수를 사용한다. 다음은 C 문자열과 자바의 jstring 사이의 문제를 해결한 C++ 코드이다.

JNIEXPORT void JNICALL Java_WinDialogBox_setTextField(JNIEnv *env, jobject obj, jstring str) {
    char strBuff[128];
    const char *sz = env->GetStringUTFChars(str, 0) ;
    strcpy(strBuff, sz) ;
    env->ReleaseStringUTFChars(str, sz) ;
}

JNIEXPORT jstring JNICALL Java_WinDialogBox_getTextField(JNIEnv *env, jobject obj) {
    char strBuff[128] = "Hello Java VM";
    return env->NewStringUTF(strBuff) ;
}

똑같은 내용이지만 C에서 호출한다면, 즉 확장자가 .c인 프로그램에서는 JNI 함수를 호출하는 방법이 약간 달라진다. 다음은 같은 내용의 C 코드이다.

JNIEXPORT void JNICALL Java_WinDialogBox_setTextField(JNIEnv *env, jobject obj, jstring str) {
    char strBuff[128];
    const char *sz = (*env)->GetStringUTFChars(env, str, 0) ;
    strcpy(strBuff, sz) ;
    (*env)->ReleaseStringUTFChars(env, str, sz) ;
}

JNIEXPORT jstring JNICALL Java_WinDialogBox_getTextField(JNIEnv *env, jobject obj) {
    char strBuff[128] = "Hello Java VM";
    return (*env)->NewStringUTF(env, strBuff) ;
}

이처럼, 같은 내용이라도 JNI 함수를 C와 C++에서 호출하는 방법에 차이가 있음을 알아두자.

(*env)->FindClass(env, “java/lang/String”); // C의 경우
env->FindClass(“java/lang/String”); // C++의 경우

이 내용들을 가지고 간단한 Win32 에디터를 만들어 보자.

먼저 <리스트 5>와 <리스트 6>에 있는 두 개의 자바 소스 파일을 만들고 각각을 컴파일한다. <리스트 5>의 WinDialgoBox.java 프로그램은 세 개의 네이티브 메쏘드를 선언하고 있는 클래스이며, 실제 이 네이티브 메쏘드의 몸체는 WinDialogBox.DLL 안에 만들어질 것이다. 그리
고 앞과 마찬가지로 자바 헤더 파일을 만든다.

C:\jni>javah -jni WinDialogBox

이렇게 하면 WinDialogBox.h가 생성된다. 이 파일은 세 개의 네이티브 함수에 대한 선언을 포함하고 있다. 그리고 비주얼 C++에서 ‘WinDialogBox’란 프로젝트를 만든다(‘Win32 Dynamic-Link Library’ 타입으로 생성하며, 위저드의 첫 단계에서 ‘A Simple Projcet’를 체크한다). 그리고 javah 프로그램이 생성한 WinDialogBox.h 파일을 프로젝트에 포함시키고, Win DialogBox.cpp 파일을 <리스트 7>과 같이 편집한다.

이 C++ 프로그램의 내용을 잠깐 살펴보자. Java_WinDialog Box_doModal() 함수는 다이얼로그를 화면에 표시하는 함수이다. Java_WinDialogBox_setTextField()는 자바 코드에서 문자열을 넘겨 전역 문자 배열에 저장하는 함수이며, Java_WinDialogBox _getTextField() 함수는 전역 문자 배열의 내용을 jstring 타입으로 변환해 자바로 문자열을 리턴하는 함수이다.

DialgProc() 함수는 다이얼로그 프로시저이다. 이 다이얼로그 프로시저는 두 개의 메시지(WM_INITDIALOG, WM_COMMAND)를 처리하고 있다. 다이얼로그가 화면에 나타날 때, 즉 WM_INIT DIALOG 메시지가 전달되면 SetDlgItemText() 함수를 이용해 전역 문자열에 있던 문자를 에디트 컨트롤에 지정한다.

반대로, 버튼이 눌리면 GetDlgItemText() 함수를 이용해 편집된 문자열을 얻어와 전역 문자열에 저장한다. 이 저장된 값은 getTextField() 메쏘드를 통해 자바 프로그램으로 리턴되어 편집된 내용을 출력하게 된다.

이 C++ 코드가 생성하는 DLL은 리소스를 가지고 있다. 따라서 프로젝트에 리소스를 추가하고 다이얼로그 박스를 생성한다. 다이얼로그 박스는 <화면 3>과 같이 디자인한다. 이 다이얼로그 박스에는 IDC_EDIT_FIELD란 ID를 갖는 에디트 컨트롤이 포함돼 있다. 프로그램을 수행하면 <화면 4>와 같은 Win32 다이얼로그 박스가 나타난다. 여기에 입력된 내용은 다시 자바 프로그램으로 리턴돼 <화면 5>와 같이 도스창 화면에 출력된다.

화면 5

화면 5


지금까지 기본적인 데이터 타입인 jint, jstring 등의 데이터를 자바와 C 코드에서 넘겨주고 받는 방법들을 살펴보았다.

참고로 다음 코드는 자바에서 Win32 애플리케이션이 프로그램의 상태와 관련된 정보를 저장하기 위해 사용하는 Win32 API 함수 중의 하나인 GetPrivateProfileString()을 구현해본 것이다(레지스트리의 등장으로 요즘은 거의 사용하지 않지만). JNI는 이렇듯, 운영체제의 특징적인 서비스를 이용할 수 있는 장점이 있다.

jstring JNICALL Java_Win32Profile_GetPrivateProfileString(JNIEnv *jni, jclass, jstring section, jstring key, jstring defaut, jint maxLen, jstring name) {
    char returned[4096];
    LPCSTR file;
    LPCSTR szSection;
    LPCSTR szKey;
    LPCSTR szDefault;
    jint ret = 0;
    file = jni->GetStringUTFChars(name, 0);
    szSection = jni->GetStringUTFChars(section, 0);
    szKey =jni->GetStringUTFChars(key, 0);
    szDefault=jni->GetStringUTFChars(defaut, 0);

    if (0 != strcmp(file, “win.ini”))
        ret = GetPrivateProfileString(szSection, szKey, szDefault, returned, maxLen, file);
    else
        ret = GetProfileString(szSection, szKey, szDefault, returned, maxLen);
    if (*returned == ‘\0’)
        strcpy(returned, szDefault);
    jni->ReleaseStringUTFChars(name, file);
    jni->ReleaseStringUTFChars(section, szSection);
    jni->ReleaseStringUTFChars(key, szKey);
    jni->ReleaseStringUTFChars(defaut, szDefault);
    return jni->NewStringUTF(returned);
}

주의 깊은 독자라면 이 프로그램을 테스트하면서 커다란 문제점을 발견할 수 있을 것이다. 그것은 바로 개발자들을 끊임없이 괴롭혀온 문제 중의 하나인 한글 문제이다.

자바에서 한글이 포함된 문자열을 C로 보내면(UTF-8 포맷으로), C에서는 이를 직접 사용할 수 없다. 또한 다이얼로그 박스에서 한글이 입력돼 자바로 전달하면 이것도 나타나지 않거나 ‘???’로 표시된다. 이런 문제는 왜 생기며, 어떻게 해결할 수 있을까?

화면 3

화면 3


화면 4

화면 4



★ JNI와 한글 문제

자바는 기본적으로 모든 문자열을 유니코드(Unicode)로 처리한다. 유니코드는 한 글자를 16비트로 표현하는 표준 코드로 바이트 문자열에 비해 크기가 커지는 단점이 있지만, 다양한 언어를 자유롭게 지원할 수 있다는 장점이 있다.

- 자바 : 유니코드(Unicode)
- JNI : UTF-8
- C/C++ : 바이트 문자열(KSC5601)

하지만 보통 Win32, C/C++ 환경에서 사용하는 문자열은 유니코드가 아닌 바이트 문자열(KSC5601)이다. 더구나 JNI에서 사용하는 jstring은 UTF-8 포맷으로 인코딩된 문자열이므로 직접 C 코드에서 사용하는 데는 어려움이 있다. 앞의 프로그램에서 해본 대로 영문자의 경우는 아무런 문제가 없다.

UTF-8 포맷이 영문자에 대해서는 1바이트로 인코딩되기 때문에 바이트 문자열과 UTF-8 포맷의 문자열은 사실상 같은 것이라고 보아도 무방하다. 하지만 한글의 경우는 다르다. 예를 들어보자. ‘한’이란 문자가 있다 하자. 이 글자는 <표 3>에서 볼 수 있듯 전혀 다른 형태로 저장된다.

만일 JNI를 통해 한글을 쓰고 싶다면, JNI를 통해 넘어온 jstring 문자열은 UTF-8 포맷이므로 이를 KSC5601 코드로 바꾸는 작업이 필요하다. 이 작업을 C 프로그램에서 해야 한다면, 사실 배보다 배꼽이 더 큰 일이다. 더욱이 UTF-8은 KSC5601 코드를 변환한 것이 아니라 유니코드를 인코딩한 것이므로 일은 더욱 복잡해진다.

자바에는 이 코드 변환에 대한 해결책이 이미 준비돼 있다. 따라서 이것을 C에서 해결하려 하기보다는 C에서는 단순하게 바이트 문자열만을 다루고, 유니코드와 UTF-8은 자바 코드에서 다루도록 하는 것이 프로그래밍을 훨씬 간단하게 한다.

즉, jstirng을 사용하지 않고, 바이트 배열을 주고받는 것이다. 경험으로 미루어 볼 때 자바와 C 간의 데이터 교환에서 가장 중요한 것은 사실 jstring보다 바이트 배열이다. 바이트 배열은 임의의 데이터 형태를 모두 전달할 수 있기 때문이다. 이것은 비단 JNI에만 국한된 문제는 아니다.

서블릿(sublet), 자바 애플리케이션(application), 애플릿(applet) 모두 외부에서 입력된 문자열을 처리해야 할 필요가 있기 때문에 자바 프로그래머라면 언제고 한번쯤은 부딪히게 되는 문제이다.

자바에서 외부에서 입력된 바이트 문자열을 자바 내부에서 사용하는 유니코드 혹은 UTF-8로 변환하는 것은 의외로 간단하다. <리스트 8>은 표준 입력에서 바이트 문자열을 입력받아 내부적으로 사용되는 String 객체로 다시 생성하는 방법을 보이고 있다.

System.in.read()를 통해 바이트 단위로 입력된 바이트 배열은 String 생성자를 통해 유니코드로 전환된다. String 생성자를 이용할 때, 바이트 배열이 인코딩된 방식을 지정해줄 수 있다.

이 생성자가 생성해낸 String 객체는 유니코드 문자열을 가지게 된다. 이 내용을 출력해 보면 똑같은 내용이 출력된다(<화면 6>). 자바에서 표준 출력에 출력할 때, 플랫폼 환경에 맞추어 다시 인코딩한 후 출력하기 때문이다.

화면 6

화면 6


String(byte[] bytes, String enc); // 바이트 문자열을 유니코드 문자열로 전환
byte[]getBytes(String enc); // 유니코드 문자열을 바이트 문자열로 전환

이 두 개의 메쏘드는 String 클래스의 메쏘드로, 코드 변환에 유용하게 사용된다. enc에는 인코딩된 방식을 써주면 된다. 예를 들어 Win32 환경에서 가장 많이 사용하는 KSC5601 코드의 경우, 바이트 문자열을 유니코드로 전환하려면 다음과 같이 한다.

byte buffer [] = new byte[200];
...
String uni = new String(buffer, KSC5601);

또한 유니코드를 KSC5601 바이트 문자열로 바꾸고자 한다면, 다음과 같이 해주면 된다.

byte[] buffer;
String uni = “한글”;
buffer = uni.getBytes(KSC5601);

앞에서 작성해 본 WinDialogBox.java 프로그램을 한글 처리를 염두에 두고 다시 작성해 보자. 파일 한글 처리가 문제되는 것은 외부에서 입력받는 내용이 유니코드 문자열이 아니라, 일반적으로 KSC5601 코드의 바이트 문자열이라는 점에 있다. 따라서 앞서 이야기한 KSC5601 바이트 코드와 유니코드 간의 변환을 이용하되, 자바 코드 외부에서 입력 혹은 전달되는 내용은 모두 바이트 배열로 처리하는 것이 바람직하다.

JNI에서는 <표 1>의 JNI에서 볼 수 있는 데이터 타입 이외에 다음과 같은 데이터 타입을 지
원한다.

struct _jobject;
typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;

객체나 각 기본 타입의 배열들에 대한 지원도 하고 있다. 여기서는 jbyteArray 타입을 이용할 것이다.

<표 3> ‘한’에 대한 코드
바이트 문자열(KSC5601)
유니코드
UTF-8
0xC7 0xD1
0xD55C
0xED 0x95 0x9C
2바이트
16비트 코드
3바이트


★ JNI에서 자바 배열 객체

자바의 배열은 자바의 객체이다. JNI를 통해 넘겨받은 자바의 배열을 다루기 위해 JNI가 제공하는 함수들에 대해 알아보자.

모든 자바 배열은 크기 필드를 갖고 있다. 다음 함수는 자바 배열 요소 개수를 알아내는 함
수이다.

jsize GetArrayLength(JNIEnv *env, jarray array);

배열의 각각 요소들을 다루기 위해서는 다음 JNI 함수가 필요하다.

jobject GetObjectArrayElement(JNIEnv *env, jarray array, jsize index);
void SetObjectArrayElement(JNIEnv *env, jarray array, jsize index, jobject value);

이름에서도 알 수 있듯이 이 두 개의 함수는 배열의 요소를 얻거나 요소를 할당하는 함수이다. 배열에서 요소 값을 읽어올 때는 다음과 같은 GetArrayElements() 함수를 이용한다. 이 함수의 세번째 인자 isCopy는 자바 VM에서 배열의 요소들이 기억장소에 연속적으로 배치되어 있는지의 여부를 jboolean값으로 리턴한다. 이 값이 참(JNI_ TRUE)이라면 연속적으로 배
열된 복사본이라는 사실을 알 수 있고, 거짓(JNI_FALSE)이라면 실제 배열 요소를 다루어야
한다.

NativeType GetArrayElements(JNIEnv *env, jarray array, jboolean *isCopy);

<표 4>는 GetArrayElements() 함수의 실제 함수 이름과 타입별 리턴 타입, 그리고 그에 해당하는 자바의 배열 타입을 정리한 것이다. 이 함수를 사용하고 나면, ReleaseArrayElements() 함수를 사용해 복사본을 기억장소에서 해지해야 한다. 이 함수의 실제 함수 이름과 리턴 타입도 <표 5>에 정리해 놓았다.

<표 4> 배열을 다루기 위한 JNI의 GetArrayElements 함수
JNI 함수
리턴타입
자바 배열 타입
GetBooleanArrayElements()
jboolean *
boolean[]
GetByteArrayElements()
jbyte *
byte[]
GetCharArrayElements()
jchar *
char[]
GetShortArrayElements()
jshort *
short[]
GetIntArrayElements()
jint *
int[]
GetLongArrayElements()
jlong *
long[]
GetFloatArrayElements()
jfloat *
float[]
GetDoubleArrayElements()
jdouble *
double[]

<표 5> 배열을 다루기 위한 JNI의 ReleaseArrayElements 함수
JNI 함수
네이티브 배열 타입
자바 배열 타입
ReleaseBooleanArrayElements()
jboolean *
boolean[]
ReleaseByteArrayElements()
jbyte *
byte[]
ReleaseCharArrayElements()
jchar *
char[]
ReleaseShortArrayElements()
jshort *
short[]
ReleaseIntArrayElements()
jint *
int[]
ReleaseLongArrayElements()
jlong *
long[]
ReleaseFloatArrayElements()
jfloat *
float[]
ReleaseDoubleArrayElements()
jdouble *
double[]

void ReleaseArrayElements(JNIEnv *env, jarray array, NativeType elems, jint mode);

이 함수는 mode에 세 가지 다른 값을 지정할 수 있다.

0 : 변경된 내용을 반영하고, 복사본의 기억장소를 해지한다.
JNI_COMMIT : 변경된 내용을 반영만 하고, 복사본의 기억장소를 해지하지 않는다.
JNI_ABORT : 변경된 내용을 반영하지 않고, 복사본의 기억장소를 해지한다.

이제 WinDialogBox의 두번째 버전(WinDialogBox2)을 만들어 보자. 이 프로그램을 만드는 방법은 앞서 보았던 WinDialogBox 프로그램을 만드는 방법과 꼭 같다. 자바 소스 프로그램을 <리스트 9>, <리스트 10>과 같이 만들고 컴파일한다. 그 다음, javah 명령을 이용해 WinDialogBox2.h 파일을 생성한다.

C:\jni> javac TestWinDialogBox2.java
C:\jni> javac WinDialgoBox2.java
C:\jni> javah jni WinDialgoBox2

이제 DLL을 생성하기 위한 프로젝트로 WinDialogBox2를 새롭게 만든다. 여기에 다이얼로그 박스를 만들고, WinDialogBox2.cpp 프로그램을 <리스트 11>과 같이 작성한다. 빌드하여 만들어진 WinDialgoBox2.DLL 파일을 클래스 파일이 있는 곳에 복사하고, 다음과 같이 자바 프로그램을 수행시킨다.

C:\jni> java TestWinDialogBox2

<화면 7>은 TestWinDialogBox2 프로그램을 수행시킨 화면이다. 이제 한글 문제가 완전히 해
결됐다.

화면 7

화면 7


이처럼 JNI를 이용해 C 프로그램과 데이터 교환을 할 때 가장 유용하게 사용할 수 있는 것이 바이트 배열이다.


★ C/C++에서 자바 사용

지금까지 우리는 자바에서 C/C++ 코드를 사용하는 방법을 살펴보았다. 그렇다면 반대로 C/C++에서 VM을 이용하려면 어떻게 해야 할까? 이 부분은 자바에서 C 코드를 호출하는 것에 비해 상대적으로 중요하지 않기 때문에 자세하게 다루지는 않지만, 독자 여러분이 찾아볼 수 있을 정도의 간략한 개요를 정리해 보겠다.

C/C++에서 VM을 사용하기 위해 가장 먼저 해야 할 것은 C 코드에서 자바 VM을 생성하는 일이다. JNI는 C/C++ 애플리케이션에 자바 VM을 포함(embedding)할 수 있도록 지원한다. C/ C++ 애플리케이션에 VM을 생성하면, VM의 모든 클래스와 메쏘드를 이용할 수 있다.

환경에 대한 디폴트 설정 값을 얻어오기 위해 JNI_GetDefaultJavaVMInitArgs() 함수를 사용한다(JDK 1.1 이하). 이 함수는 JDK1_1InitArgs 구조체 포인터를 인자로 받는데, 이 구조체에는 다음과 같이 버전(vm_args.version) 값을 지정해 주어야 한다.

JDK1_1InitArgs vm_args;
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs (&vm_args);

버전 값은 다음과 같이 정의돼 있다(JDK 1.2 이상). 버전으로 사용된 값 0x00010001의 의미는 1.1.2 이상되는 버전의 VM이면, 애플리케이션에서 사용할 수 있다는 의미가 된다.

#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002

그런데 이 JNI_GetDefaultJavaVMInitArgs() 함수는 JDK 1.2 이상에서는 쓰이지 않는다. C/C++에서 VM을 사용하려면 먼저 JNI_CreateJavaVM() 함수를 통해 VM을 생성한다.

JavaVM *jvm;
JNIEnv *env;
jint rc = JNI_CreateJavaVM (&jvm, &env, &vm_args);

이렇게 해 JVM이 생성되면, JNI 함수 중 FindClass()와 Call StaticVoidMethod()를 이용해 자바 클래스와 메쏘드를 호출한다. JVM 사용이 끝나면, DestroyJavaVM() 함수를 써서 VM을 삭제한다.

jvm-> DestroyJavaVM ()

C/C++에서 대략 이런 과정을 통해 VM을 사용하게 된다. C/C++에서 자바 클래스를 이용하는 것이 실제로 많이 요구되지는 않는다.

지금까지 자바에서 C/C++ 코드를 호출하는데 필요한 내용들을 살펴보았다. 특히, 유니코드 관련 내용과 바이트 배열 처리 부분은 상당히 유용한 코드가 될 것이다.

이 JNI 코드 작성을 통해 우리는 몇 가지 사실을 알 수 있다. 우선, 자바에서 모든 Win32 호출을 이용할 수 있다는 점이다. 이것은 유닉스에서도 마찬가지이다. 모든 절차는 똑같으며, 단 유닉스에서는 DLL 대신 공유 라이브러리(shared library)를 사용한다는 점이 다르다.

이 글은 애플릿에서의 문제점은 다루고 있지는 않다(애플릿에서 JNI는 보안과 인증 문제를 갖고 있기 때문에 이 글에서 모두 다루기는 힘들다). C/C++로 액티브X 컴포넌트를 개발하는 프로그래머들이 비주얼 베이직 개발자들을 지원하듯, JNI는 C/C++ 개발자들이 자바 개발자들을 지원하는 창구 역할을 한다.

아무튼 자바에서 기존의 C/C++로 작성된 코드를 사용할 수 있고, 자바와 C/C++ 간의 데이터 교환이 가능하며, Win32 API를 모두 이용할 수 있다는 점은 JNI의 큰 매력이 아닐 수 없다.
컴퓨터/프로그래밍/Java l 2008/07/24 11:00

'language > JAVA' 카테고리의 다른 글

Decompiler jad  (0) 2010.07.22
& 0xFF를 사용한 unsigned byte 및 int 비교 방법  (0) 2010.07.21
JNI (Java Native Interface) 란 ?  (0) 2009.06.30
블로그 이미지

란마12

,

1. JNI (Java Native Interface) 란 ?

- 자바가 다른 언어로 만들어진 어플리케이션과 상호 작용할 수 있는 인터페이스를 제공한다.

- 자바가상머신(JVM)이 원시 메소드(native method)를 적재(locate)하고 수행(invoke)할 수 있도록 한다

- JNI가 자바가상머신내에 포함됨으로써, 자바가상머신이 호스트 운영체제상의 입출력, 그래픽스, 네트워킹, 그리고 스레드와 같은 기능들을 작동하기 위한 로컬시스템호출(local system calls)을 수행할 수 있도록 한다.

* 쉽게 말해 Java와 다른 언어를 연동하는 솔루션입니다.

 

[그림1] C로 만들어진 Library와 JAVA를 연결해주는 JNI

 

 

 

2. Why do you need JNI ?

 

자바 네이티브 메쏘드(Java Native method, 이하 JNI)는 다른 언어로 작성된 코드를 자바에서 호출하도록 만들어진 규약이다. 현재는 C/C++에 대한 호출만을 정확하게 지원한다. 어떻게 보면 JNI는 자바가 만들어진 철학과 정반대되는 것이다.

 

그러나. Java에도 한계가 있다.

 

1. 속도 문제가 있는 계산 루틴
 > 자바가 Native Code(플랫폼에 종속적인 기계어 코드)에 비해 느리다.

2. 자바에서 하드웨어 제어

3. 자바에서 지원되지 않은 특정 운영체제 서비스
 > 자바의 클래스 라이브러리는 방대하고 다양한 서비스를 제공하지만, 특정 플랫폼에서 제공하는 고유의 서비스의 기능을 모두 포함할 수는 없다. 특히, 특수한 목적으로 제작된 하드웨어를 자바에서 제어해야 할 필요가 있다고 한다면, 자바만으로 해결하기는 힘들다.

4. 기존의 프로그램에서 자바가 제공하는 서비스를 이용
 > 기존에 작성된 프로그램이나 기존의 시스템(legacy)과의 연계 문제

 

∴ JNI를 써서 해결해보자.

 

 

3. C를 이용한 JNI 예제

VC++을 이용해 C문법으로 작성되어 만들어진 DLL을 로딩하여 Java에서 사용해보겠습니다.

 

1단계 : Native Method를 선언하는 자바 클래스 작성
2단계 : 1단계에서 작성한 클래스 컴파일
3단계 : javah를 사용해서 Native Method가 사용할 헤더 파일 생성
4단계 : C언어로 Native Method 실제 구현
5단계 : C 코드와 헤더 파일을 컴파일
6단계 : 자바 프로그램 실행


 

1단계 : Native Method를 선언하는 자바 클래스 작성

Java 소스 파일 : HelloJni_Jsource.java

 

import java.util.*;

class HelloJniClass {
   native void Hello();

  static {  System.loadLibrary("Hello_DLL");   }

  public static void main(String args[]) {   
      HelloJniClass myJNI=new HelloJniClass();
      myJNI.Hello();
   }
}

 

// 아래는 좀 위의 내용 보충 그림



 


2단계 : 1단계에서 작성한 클래스 컴파일



* 컴파일시에는 일반 java 컴파일때와 마찬가지로 환경변수 셋팅이 되어 있어야 합니다.
 -> Path가 JDK의 Javac.exe가 있는 폴더에 설정되어 있어야 합니다.

 

 

3단계 : javah를 사용해서 Native Method가 사용할 헤더 파일 생성

 

HelloJniClass.h을 열어보면

JNIEXPORT void JNICALL Java_HelloJniClass_Hello  (JNIEnv *, jobject);
위의 함수를 Implement만 해서 DLL을 만들면 됩니다. (4단계)


4단계 : C언어로 Native Method 실제 구현(1)

1) VC++ 프로젝트 만들기 : Win32용 DLL 프로젝트로 만듭니다.
New - Projects : Win32 Dynamic-Link Library

2) Add Files Projects : HelloJniClass.h 파일 추가

3) Projects Setings(Alt+F7)
   - Link탭에 Output file Name : 1단계의 2. 라이브러리 적재시 작성한 DLL파일명(Hello_DLL.dll)
   - C/C++탭 Preprocessor 카테고리의 Additional Include directories
       JDK의 Include폴더와 Include폴더 밑의 win32폴더

          예) C:\Program Files\Java\jdk1.5.0_03\include\,
             C:\Program Files\Java\jdk1.5.0_03\include\win32

 

 


 

 

4. 값의 전달과 리턴

 

1단계 : Java 소스 파일 StringPass_Jsource.java
* 일반 자바 메쏘드 선언과 동일합니다.

class JNI_Message {
   native byte[] Message(String input);

  // 라이브러리 적재(Load the library) 

  static {
    System.loadLibrary("Msg_DLL");
  }

 

  public static void main(String args[]) {   
 byte buf[];

    // 클래스 인스턴스 생성(Create class instance)
    JNI_Message myJNI=new JNI_Message();

    // 원시 메소드에 값을 주고 받음
    buf = myJNI.Message("Apple");
 
 System.out.print(buf); // 받은값 출력
 }
}

 


2단계 : 컴파일
 javac StringPass_Jsource.java

 

3단계 : header파일 생성
 javah JNI_Message

 

4단계 : method구현 : StringJNIDLLSource.c

#include <stdio.h>
#include <jni.h>
#include <string.h>
#include "JNI_Message.h"

JNIEXPORT jbyteArray JNICALL Java_JNI_1Message_Message (JNIEnv * env, jobject jobj, jstring input)
{
    jbyteArray jb;
    jboolean iscopy;
    char* buf;
    static char outputbuf[20];

    buf=(*env)->GetStringUTFChars(env, input, &iscopy);  // 입력 String 읽어오는 함수
    printf ("\nDLL receive Data from JAVA : %s\n",buf);   // 입력받은 내용을 출력
    strcpy(outputbuf,"Delicious !!\n");
    jb=(*env)->NewStringUTF(env, outputbuf);  // 출력할 내용의 java버퍼에 output버퍼값을 셋팅

   return(jb); // java버퍼 리턴
}

 

 

(*env)->함수명 형태로, JAVA의 메쏘드를 C에서 이용할수 있습니다.
* JAVA는 C로 문자열을 넘겨줄때 UTF-8형태를 사용합니다.
* cpp로 컴파일하지 말고 c로 컴파일 한다. 

 

5단계 : 실행

C:\test\C_JNI\Paramerter Pass>java JNI_Message

DLL receive Data from JAVA : Apple
Delicious !!

 

 

 


5. KVM ? KNI ?

KVM은 J2ME의 일부로서 작고 자원이 한정된 기계장치를 위해 설계된 소형 JVM.
JVM에서는 JNI가 KVM의 KNI가 있다.


 

6. 기타프로그래밍 이슈들
참고 URL :http://www.javastudy.co.kr/docs/jhan/javaadvance/jni.html

언어적 이슈(Language Issues)
메소드 호출(Calling Methods)
필드의 참조(Accessing Fields)
스레드와 동기화(Threads and Synchronization)
메모리 이슈(Memory Issues)
수행(Invocation)
스레드 연결(Attaching Threads)


http://sinuk.egloos.com/2676307

'language > JAVA' 카테고리의 다른 글

Decompiler jad  (0) 2010.07.22
& 0xFF를 사용한 unsigned byte 및 int 비교 방법  (0) 2010.07.21
JAVA Native Method (JNI)  (1) 2009.07.01
블로그 이미지

란마12

,