그 밖의 유용한 VBScript 5.X 버전의 기능들

등록일시: 2002-11-03 16:19,  수정일시: 2018-04-07 23:22
조회수: 16,225
본문은 최초 작성 이후, 약 22년 이상 지난 문서입니다. 일부 내용은 최근의 현실과 맞지 않거나 동떨어져 있을 수 있으며 문서 내용에 오류가 존재할 수도 있습니다. 또한 본문을 작성하던 당시 필자의 의견과 현재의 의견에 많은 차이가 존재할 수도 있습니다. 이 점, 참고하시기 바랍니다.

지금까지 일련의 글들을 통해서 VBScript 5.X 버전에서 제공되는 여러 가지 새로운 기능들 중에서도 가장 중요한 위치를 점하고 있는 두 가지 기능, 즉 정규 표현식(Regular Expression)과 클래스(Class)에 관해서 살펴봤다. 이 두 가지 기능이야말로 VBScript가 5.X 버전대에서 보여주는 변화의 핵심이라고 말할 수 있을 것이다.

그러나 지극히 당연한 얘기겠지만 정규 표현식과 클래스가 VBScript 5.X 버전의 변화의 전부는 아니며 그 밖에도 유용한 기능들이 폭넓은 수준으로 지원되고 있는데, 바로 With 문이나 Execute 문(또는 ExecuteGlobal 문), Eval() 함수, 그리고 Timer() 함수 등이 그 대표적인 사례이다. 따라서, 본문에서는 바로 이런 기능들에 대해서 살펴보도록 하겠다.

Execute 문과 ExecuteGlobal 문, 그리고 Eval() 함수

작업을 하다 보면 종종 다음과 같은 상황을 접하게 된다. 가령, 어떤 작업을 처리하는데 대략 세 네 가지 정도의 상황이 발생할 수 있다고 가정해보도록 하자. 당연히 그 각각의 상황마다 프로그램의 처리는 달라진다. 이를테면, 수학적인 문제를 처리한다고 가정해 볼 때, 상황 A의 경우에는 a + b라는 공식을 사용해서 값을 처리하고, 상황 B의 경우에는 a + b - c라는 공식을 사용해서 값을 처리해야 하며, 상황 C의 경우에는 a * b * 1.2라는 공식을 사용해서 값을 처리해야 한다고 가정해보자.

일반적인 방법으로 이 문제를 해결하려면 아마도 이런 작업을 처리하는 사용자 함수를 하나 만들어야 할 것이다. 그리고, a, b, c의 값과 상태값을 이 함수에 인자로 넘겨주면, 내부 코드에서는 Case 문이 사용될 확률이 높다. 그런 다음, 인자로 넘겨진 상태값에 따라 적절한 공식을 사용하여 값을 구하고 그 결과를 리턴해준다. 이것이 보통 상식적인 처리 방법이다. 아마도 실무에서 이와 가장 유사한 처리 사례는 예금이나 채권 등의 이자 계산에서 찾아볼 수 있지 않을까 한다.

그런데, 이런 처리 방식은 본질적인 문제점을 갖고 있다. 무슨 얘긴가 하면 코드가 완성된 후에, 발생할 수 있는 상황이 늘어나거나 상황에 따른 공식이 변경되면 코드 자체를 수정해야 한다는 점이다. 또한 극단적인 예로, 발생할 수 있는 상황이 세 네 가지가 아닌 수 십여 가지 혹은 수 백여 가지라면 동일한 갯수 만큼 Case 문이 사용되야 한다. 이런 프로그래밍 방식은 구조적인 면에서 가급적 피하는 것이 바람직하다.

이 상황을 타개하기 위한 방법에는 어떤 방법이 있을까? VBScript 5.X 이전의 버전에서는 그냥 그대로 Case 문을 경우의 수 만큼 작성하는 방법 외에는 별다른 방법이 없었다. 아무리 고민을 해봐도 더 이상 해결책이 없는 것이다. 그런데, 놀라운 점은 JavaScript에는 이런 상황에 유용하게 사용할 수 있는 기능이 이미 오래전부터 존재하고 있었다는 사실인데, 바로 eval() 함수가 그것이다.

아마도 JavaScript에 능숙한 분들은 이미 이 함수에 관해서 잘 알고 계실 것이다. 이 eval() 함수는 코드 자체를 문자열 변수로 다룰 수 있게 해준다. 백 번 설명하는 것보다 코드를 한 번 보는 것이 더 이해가 쉬울 것이다. 결론부터 얘기하자면 다음의 두 코드는 100% 동일하다.

  <script language="JavaScript" type="text/javascript">
  <!--

  var mydate = new Date();
  ... 생략 ...
  
  //-->
  </script>

이것이 일반적인 코드다. 그렇다면 이번에는 eval() 함수를 사용하는 경우를 살펴보도록 하자.

  <script language="JavaScript" type="text/javascript">
  <!--

  eval("var mydate = new Date();");
  ... 생략 ...
   
  //-->
  </script>

이미 말했듯이 이 두 코드의 실행 결과는 100% 동일하다. 여기서 중요한 사실은 eval() 함수에 인자로 넘어가는 코드가 문자열 형태를 갖고 있다는 점이다. 따라서, 코드에서 프로그래머 임의로 편집하는 것이 가능해진다. 한 마디로 eval() 함수에 인자로 넘겨질 코드 자체를 편집할 수 있게 되는 셈이다. 마치, SQL 구문을 문자열 형태로 편집하는 것처럼.

그렇다면 이번에는 eval() 함수가 실무에서 사용되는 조금 더 구체적인 사례를 간단하게 한 가지 들어보도록 하겠다. 여러분이 작성하고 있는 임의의 HTML 문서에 myForm이라는 이름을 가진 폼(Form)이 하나 존재하고 있고, 그 내부에는 이름이 myTextbox_n인 n개의 텍스트박스(Textbox)가 존재한다고 가정해보자. 여기서 n은 텍스트박스의 순서를 의미하는 숫자값으로서 1부터 시작한다. 이때 JavaScript를 이용해서 그 n개의 텍스트박스에 순서대로 1부터 n까지의 초기값을 입력해야 할 경우, JavaScript를 처음 다뤄보시는 분들이라면 아마도 다음과 같은 코드가 머리 속에 떠오르기 쉬울 것이다.

  <script language="JavaScript" type="text/javascript">
  <!--

  document.myForm.myTextbox_1.value = "1";
  document.myForm.myTextbox_2.value = "2";
  document.myForm.myTextbox_3.value = "3";
  ... 중략 ...
  document.myForm.myTextbox_n.value = "n";
   
  //-->
  </script>

그러나 실무에서는 이런 코드는 좀처럼 사용되지 않는다. 무엇보다 이 코드는 n이라는 불확실한 값에 의지해서 프로그래밍되었기 때문에 상황이 바뀔 때마다 매번 코드도 수정해야 한다. 또한 위에서 얘기했던 VBScript의 사례와 동일하게 코드의 줄 자체가 n개 만큼 요구되므로 n의 값이 매우 큰 경우 코드를 작성하는 일 자체가 힘겨워진다. 그렇다면 이번에는 다음 코드를 살펴보도록 하자.

  <script language="JavaScript" type="text/javascript">
  <!--
  
  var tempObj;
  
  for (var i = 1; i <= n; n++)
  {
    tempObj = eval("document.myForm.myTextbox_" + i);
    tempObj.value = i;
  }
   
  //-->
  </script>

이 코드는 동일한 문제를 eval() 함수를 사용하여 해결하고 있다. 휠씬 더 깔끔하고 간단한게 문제를 처리하고 있다는 것을 알 수 있다. 이처럼 eval() 함수를 사용하면 코드 그 자체를 문자열처럼 다룰 수 있으므로 문제를 해결하는데 큰 도움이 된다.

이제 다시 VBScript에 관한 얘기로 돌아가 보자. 만약 방금 살펴본 JavaScript의 eval() 함수와 동일한 기능이 VBScript에서도 지원된다면 당면한 문제를 해결하는데 큰 도움이 될 것이다. 필자가 이런 얘기를 하는 이유는? 그렇다. 바로 VBScript 5.X 버전부터는 JavaScript의 eval() 함수와 동일한 기능이 지원되기 때문이다. 다만, 한 개가 아닌 세 개의 구문이 지원되는데 Execute 문과 ExecuteGlobal 문, 그리고 Eval() 함수가 바로 그것이다.

이렇게 여러 개의 구문이 지원되는 데는 약간의 이유가 있는데, 그 중 첫 번째 이유는 VBScript의 = 연산자가 값 할당과 비교의 두 가지 의미를 지니고 있기 때문에 이 두 경우를 명확하게 구분하기 위함이고, 다른 한 가지 이유는 코드 그 자체의 인스턴스에 관한 문제 때문이다. 그냥 쉽게 생각해서 JavaScript의 eval() 함수를 조금 더 세분화했다고 보면 맞을 것이다.

첫 번째로 살펴볼 구문은 Eval() 함수다. VBScript의 Eval() 함수와 JavaScript의 eval() 함수는 그리 다른 점이 없다. 다만, VBScript의 Eval() 함수는 이를테면 수식 계산 전용이라고 말할 수 있다. 다음의 두 코드는 역시 100% 동일한 실행 결과를 보장한다.

<%

  '** 일반적인 방법을 사용하여 1 + 2 의 결과값을 얻는다.
  Response.Write "<font size=-1>" & (1 + 2) & "</font><br>"

  '** Eval() 함수를 사용하여 1 + 2 의 결과값을 얻는다.
  Response.Write "<font size=-1>" & Eval("1 + 2") & "</font><br>"
  
%>

이 코드에서 두 가지 서로 다른 방법으로 얻은 수식 계산의 결과는 당연하게도 두 경우 모두 동일하게 3이 리턴된다. 그런데, 다음과 같은 경우는 얘기가 조금 복잡해진다.

<%

  Dim i
  
  i = 10 
  Response.Write "<font size=-1>" & Eval("i = 11") & "</font><br>"
  
%>

이 경우 문제의 근본적인 핵심은 = 연산자다. 익히 알고 있는 바와 같이 VBScript의 = 연산자는 두 가지의 기능을 갖고 있다. 그 첫 번째 기능은 좌변에 위치한 변수에 우변에 위치한 값을 대입하는 기능이고, 두 번째 기능은 좌변의 값과 우변의 값을 비교해서 값이 동일한지 여부를 파악하고 True 또는 False를 리턴해주는 비교의 기능이다. 문제가 복잡해지는 이유는, 위와 같은 경우 과연 이 두 가지 기능 중 어떤 기능이 사용될지 모른다는 점 때문이다.

결론부터 얘기하면 Eval() 함수에 인자로 넘겨진 수식에 = 연산자가 존재하는 경우, = 연산자는 항상 두 번째 기능, 즉 비교 연산자로 해석된다. 반대로 동일한 수식이 Execute 문이나 ExecuteGlobal 문에 넘겨지면 항상 첫 번째 기능, 즉 대입 연산자로 해석된다. 따라서, 위의 코드에서 = 연산자는 비교 연산자로 해석되고 결과로는 False를 리턴한다.

또 한 가지 주의할 점은 Eval() 함수에 인자로 넘겨지는 코드의 실행 결과에는 반드시 리턴값이 존재해야 한다는 점이다. Execute 문이나 ExecuteGlobal 문이 그 분류상 '문'으로 다루어지는 반면, Eval() 함수가 말 그대로 '함수'로 다뤄지는 데는 바로 이런 이유가 숨어있는 것이다. 따라서, 다음과 같은 코드는 구문 오류를 발생시킨다.

<%

  '** 구문 오류가 발생한다.
  Response.Write "<font size=-1>" & Eval("Dim strMyString") & "</font><br>"
  
%>

이런 점들로 미뤄 볼 때 VBScript 의 Eval() 함수는 그야말로 수식 계산에 최적화되었다고 말할 수 있을 것이다. 따라서, Eval() 함수를 응용하면 다음과 같은 일들이 가능해진다. 사용자들로부터 수 십개 또는 수 백개의 다양한 수식, 이를테면 금융 상품에 따른 이자 계산 공식 같은 것을 입력받아 데이타베이스에 저장해 놓고, 필요할 때마다 데이타베이스로부터 수식을 읽어들여 사용하는 것이다. 만약, 새로운 금융 상품이 만들어지거나 이자율 등이 변경되는 경우에는 코드를 수정하지 않고서도 데이타베이스에 저장된 수식을 추가하거나 수정하는 것만으로도 새로운 수식의 적용이 즉각 가능해진다.

반면, 특정한 수식 처리가 요구되는 것이 아니라 복잡하고 많은 분량의 코드 집합을 처리해야 하는 경우라면, Eval() 함수보다는 Execute 문이나 ExecuteGlobal 문을 사용하는 것이 더 바람직하다. 그 명칭에서 짐작할 수 있는 것처럼 Execute 문과 ExecuteGlobal 문은 한 가지 차이점만을 제외한다면 거의 동일한 기능을 제공해준다. 그 차이점에 관해서는 잠시 뒤에 다시 자세하게 설명하기로 하고 일단은 Execute 문을 기준으로 글을 진행해 나가도록 하겠다. 역시 마찮가지로 다음의 두 코드는 100% 동일한 실행 결과를 보장한다.

<%

  Dim i
  
  '** 일반적인 방법을 사용하여 변수 i 에 값 10 을 대입한다.
  i = 10
  Response.Write "<font size=-1>" & i & "</font><br>"
  
  '** Execute 문을 사용하여 변수 i 에 값 10 을 대입한다.
  Execute "i = 10"
  Response.Write "<font size=-1>" & i & "</font><br>"
  
%>

이미 앞에서 얘기했던 것처럼 Execute 문이나 ExecuteGlobal 문은 인자로 넘겨진 코드 문자열에 = 연산자가 존재하는 경우 항상 대입 연산자로 해석한다. 따라서, 이 코드에 사용된 Execute 문은 변수 i에 값 10을 대입하게 된다. 코드를 좀 더 확실하게 파악하려면 변수 i에 대입되는 값을 직접 변경해보면서 그 결과를 확인해보는 것이 좋을 것이다. 그러나, Execute 문이나 ExecuteGlobal 문의 진정한 능력은 이 정도 수준에서 그치지 않는다. 그 사용법에 따라서 실시간으로 아예 새로운 프로시저를 선언하거나 덮어쓰는 것이 가능해진다.

개인적으로 Execute 문과 ExecuteGlobal 문의 이런 기능은 그야말로 대단한 것이라고 생각한다. Sub 프로시저나 Function 프로시저를 코드에서 동적으로 추가하거나 덮어 쓰는 것이다! 말은 간단하지만 실로 놀라운 기능일 뿐더러 이런 기능의 응용 범위는 매우 다양하다. 사이트에 로그인한 자격 증명에 따라 이름이 동일한 프로시저가 아예 다른 프로시저로 동작한다던가, 또는 요즘 유행하는 스킨 기능을 게시판에 추가할 때 단지 이미지나 레이아웃 뿐만이 아닌 기능 자체의 스킨화도 구현이 가능해진다. 다음 코드는 myNewSub라는 이름의 새로운 Sub 프로시저를 동적으로 추가하는 코드다.

<%

  Dim strSubCode
   
  '** Sub 프로시저의 코드를 변수에 담는다.
  strSubCode = "Public Sub myNewSub()" & vbCRLF & _
               "Response.Write ""Execute 문에 의해 추가된 프로시저.""" & vbCRLF & _
               "End Sub"
               
  '** Execute 문을 사용하여 새로운 Sub 프로시저를 선언한다.
  Execute strSubCode
  
  '** 실제로 myNewSub() 프로시저를 호출한다.
  Call myNewSub()
  
%>

이 코드를 실행시켜보면 정확하게 'Execute 문에 의해 추가된 프로시저.'라는 문자열이 줄력되는 것을 확인할 수 있을 것이다. 그런데, 만약 이 코드와 같이 Execute 문을 사용해서 선언한 프로시저와 동일한 이름의 프로시저가 이미 존재하고 있었다면 어떻게 될까? 다음 코드를 보도록 하자.

<%

  Dim strSubCode
   
  '** Sub 프로시저의 코드를 변수에 담는다.
  strSubCode = "Public Sub myNewSub()" & vbCRLF & _
               "Response.Write ""Execute 문에 의해 추가된 프로시저.""" & vbCRLF & _
               "End Sub"
  
  '** 일반적인 방식으로 Sub 프로시저를 선언한다.
  Public Sub myNewSub()
    Response.Write "일반적인 방식으로 추가된 프로시저."
  End Sub
               
  '** Execute 문을 사용하여 새로운 Sub 프로시저를 선언한다.
  Execute strSubCode
  
  '** 실제로 myNewSub() 프로시저를 호출한다.
  Call myNewSub()
  
%>

코드를 자세히 살펴보면 알 수 있겠지만 이미 일반적인 방법으로 선언된 myNewSub() 프로시저를 Execute 문을 사용해서 다시 한 번 선언하고 있다. 그럼에도 불구하고 코드를 직접 실행시켜 보면 전혀 오류를 발생시키지 않고 'Execute 문에 의해 추가된 프로시저.'라는 문자열이 정상적으로 출력된다. 즉, 일반적인 방법으로 선언한 myNewSub() 프로시저가 Execute 문을 사용하여 선언된 myNewSub() 프로시저에 의해 덮어 씌여지는 것이다.

이때, 한 가지 착각하기 쉬운 점이 있는데 그것은 Execute 문의 호출 위치에 관한 것이다. 이 코드에서는 먼저 일반적인 방법으로 프로시저를 선언한 다음 Execute 문을 사용해서여 동일한 프로시저를 덮어썼다. 그런데, 만약 Execute 문을 사용하여 프로시저를 선언하고, 그 다음에 일반적인 방법으로 동일한 프로시저를 선언하면 어떻게 될까? 한 마디로 이번에는 순서를 거꾸로 하는 것이다. 여러분의 생각은 어떨지 모르겠다. 구문 오류가 발생할 것이라고 생각하는가? 아니면 나중에 호출된 일반적인 방법으로 선언한 프로시저가 활성화된다고 생각하는가?

정답은 놀랍게도 여전히 'Execute 문을 사용하여 선언한 프로시저의 인스턴스가 활성화된다.'이다. 이 결과가 믿어지지 않는다면 실제로 직접 테스트해보기 바란다. 이런 결과가 발생되는 이유는 다음과 같다. 많은 분들이 오해하고 있는 부분이기도 한데 VBScript가 아무리 스크립트 언어고 실시간으로 컴파일된다고는 하지만, 정말로 한 줄 컴파일하고 실행하고 한 줄 컴파일하고 실행하고하는 식으로 동작하지는 않는다는 점이다. 결국, VBScript 역시도 나름대로의 컴파일 단계를 갖고 있고 필자 역시도 이에 관해서 상세하게는 알지 못하지만, 큰 관점에서 보아 구문을 읽어들이고 선언된 변수와 프로시저의 코드를 메모리에 올리는 등의 작업을 하는 단계와 실제로 VBScript가 실행되는 단계, 즉 일반적으로 런타임(Runtime)이라고 부르는 단계가 서로 구분되어 존재한다.

따라서, Execute 문의 실제 코드상의 위치와는 상관 없이 일반적인 방법으로 선언된 프로시저의 코드가 메모리에 올라가는 시점은 위의 두 단계 중에서 첫 번째 단계가 된다. 그리고, Execute 문은 그 다음 단계, 즉 런타임시에 실행되어 메모리에 있는 프로시저의 코드를 덮어 쓰게 된다. 그러므로 일견 보기에는 코드에서 윗줄에 위치한 Execute 문이 먼저 실행되는 것 같지만 사실은 항상 나중에 실행되는 것이다.

마지막으로 Execute 문과 ExecuteGlobal 문간의 차이점에 대해서 알아보고 다음 주제로 넘어가기로 하겠다. 일반적으로 Class 문을 사용해서 작성한 클래스 내부에 위치한 프로시저들을 제외한 모든 VBScript의 프로시저들은 전역 인스턴스를 가진다. 그러나, Execute 문의 경우에는 사정이 조금 다르다. 다음 코드를 살펴보자.

<%

  Dim strSubCode
   
  '** Sub 프로시저의 코드를 변수에 담는다.
  strSubCode = "Public Sub myNewSub()" & vbCRLF & _
               "Response.Write ""Execute 문에 의해 추가된 프로시저.""" & vbCRLF & _
               "End Sub"
  
  '** Execute 문을 호출하는 코드를 포함한 새로운 Sub 프로시저.  
  '** 따라서 Execute 문을 호출하기 위해서는 먼저 이 myWrapperSub() 를 호출해야 한다.  
  Public Sub myWrapperSub()
    Execute strSubCode
  End Sub
    
  Call myWrapperSub()
  Call myNewSub()
  
%>

이 코드 역시 지금까지의 코드와 그다지 다른 점이 눈에 띄지 않는다. 다만 두드러지는 한 가지 중요한 차이점은 Execute 문의 호출이 또 다른 프로시저 내부에서 이뤄진다는 사실이다. 그러나, 의외로 이 코드는 실제로 실행을 시켜보면 오류가 발생한다. 그리고, 오류 메세지의 내용을 살펴본다면 myNewSub() 프로시저를 찾을 수 없다는 내용의 메세지임을 알 수 있을 것이다.

결국 이런 일이 발생하는 원인은 Execute 문의 실행 결과가 지역에 국한된 인스턴스이기 때문이다. 따라서, 이 코드와 같은 경우 Execute 문이 호출된 지역을 벗어나는 순간 myNewSub() 프로시저의 인스턴스도 사라지게 된다. 이런 결과를 원하지 않는다면 Execute 문 대신, ExecuteGlobal 문을 사용하면 된다. 이미 짐작하고 있는 것처럼 ExecuteGlobal 문의 실행 결과는 항상 전역의 인스턴스를 갖기 때문이다.

With 문

이미 With 문에 대해서는 한 차례 설명한 바 있으므로 지금 다시 얘기하지는 않도록 하겠다. With 문에 관한 내용은 다음의 링크를 참고하기 바란다.

간단한 내용이므로 이해에 그다지 어려움이 없을 것으로 생각한다.

GetLocale() 함수와 SetLocale() 함수

이 두 함수는 그 이름에서도 알 수 있는 것처럼 시스템의 로케일과 관련된 유용한 기능들을 제공해준다. 즉, GetLocale() 함수는 현재 시스템의 로케일 ID 값을 10진수로 리턴해주며, SetLocale() 함수는 전역 로케일을 설정하고 기존의 로케일 ID 값을 역시 10진수로 리턴해준다. 여기서 얘기하는 로케일 ID란 미리 정의된 임의의 32비트 값 또는 그에 해당하는 짧은 문자열을 의미하며, 그 전체 목록은 다음과 같다.

로케일 설명 짧은 문자열 16진수값 10진수값
Afrikaans af 0x0436 1078
Albanian sq 0x041C 1052
Arabic - United Arab Emirates ar-ae 0x3801 14337
Arabic - Bahrain ar-bh 0x3C01 15361
Arabic - Algeria ar-dz 0x1401 5121
Arabic - Egypt ar-eg 0x0C01 3073
Arabic - Iraq ar-iq 0x0801 2049
Arabic - Jordan ar-jo 0x2C01 11265
Arabic - Kuwait ar-kw 0x3401 13313
Arabic - Lebanon ar-lb 0x3001 12289
Arabic - Libya ar-ly 0x1001 4097
Arabic - Morocco ar-ma 0x1801 6145
Arabic - Oman ar-om 0x2001 8193
Arabic - Qatar ar-qa 0x4001 16385
Arabic - Saudi Arabia ar-sa 0x0401 1025
Arabic - Syria ar-sy 0x2801 10241
Arabic - Tunisia ar-tn 0x1C01 7169
Arabic - Yemen ar-ye 0x2401 9217
Armenian hy 0x042B 1067
Azeri - Latin az-az 0x042C 1068
Azeri - Cyrillic az-az 0x082C 2092
Basque eu 0x042D 1069
Belarusian be 0x0423 1059
Bulgarian bg 0x0402 1026
Catalan ca 0x0403 1027
Chinese - China zh-cn 0x0804 2052
Chinese - Hong Kong S.A.R. zh-hk 0x0C04 3076
Chinese - Macau S.A.R zh-mo 0x1404 5124
Chinese - Singapore zh-sg 0x1004 4100
Chinese - Taiwan zh-tw 0x0404 1028
Croatian hr 0x041A 1050
Czech cs 0x0405 1029
Danish da 0x0406 1030
Dutch - The Netherlands nl-nl 0x0413 1043
Dutch - Belgium nl-be 0x0813 2067
English - Australia en-au 0x0C09 3081
English - Belize en-bz 0x2809 10249
English - Canada en-ca 0x1009 4105
English - Carribbean en-cb 0x2409 9225
English - Ireland en-ie 0x1809 6153
English - Jamaica en-jm 0x2009 8201
English - New Zealand en-nz 0x1409 5129
English - Phillippines en-ph 0x3409 13321
English - South Africa en-za 0x1C09 7177
English - Trinidad en-tt 0x2C09 11273
English - United Kingdom en-gb 0x0809 2057
English - United States en-us 0x0409 1033
Estonian et 0x0425 1061
Farsi fa 0x0429 1065
Finnish fi 0x040B 1035
Faroese fo 0x0438 1080
French - France fr-fr 0x040C 1036
French - Belgium fr-be 0x080C 2060
French - Canada fr-ca 0x0C0C 3084
French - Luxembourg fr-lu 0x140C 5132
French - Switzerland fr-ch 0x100C 4108
FYRO Macedonian mk 0x042F 1071
Gaelic - Ireland gd-ie 0x083C 2108
Gaelic - Scotland gd 0x043C 1084
German - Germany de-de 0x0407 1031
German - Austria de-at 0x0C07 3079
German - Liechtenstein de-li 0x1407 5127
German - Luxembourg de-lu 0x1007 4103
German - Switzerland de-ch 0x0807 2055
Greek el 0x0408 1032
Hebrew he 0x040D 1037
Hindi hi 0x0439 1081
Hungarian hu 0x040E 1038
Icelandic is 0x040F 1039
Indonesian id 0x0421 1057
Italian - Italy it-it 0x0410 1040
Italian - Switzerland it-ch 0x0810 2064
Japanese ja 0x0411 1041
Korean ko 0x0412 1042
Latvian lv 0x0426 1062
Lithuanian lt 0x0427 1063
Malay - Malaysia ms-my 0x043E 1086
Malay - Brunei ms-bn 0x083E 2110
Maltese mt 0x043A 1082
Marathi mr 0x044E 1102
Norwegian - Bokm no-no 0x0414 1044
Norwegian - Nynorsk no-no 0x0814 2068
Polish pl 0x0415 1045
Portuguese - Portugal pt-pt 0x0816 2070
Portuguese - Brazil pt-br 0x0416 1046
Raeto-Romance rm 0x0417 1047
Romanian - Romania ro 0x0418 1048
Romanian - Moldova ro-mo 0x0818 2072
Russian ru 0x0419 1049
Russian - Moldova ru-mo 0x0819 2073
Sanskrit sa 0x044F 1103
Serbian - Cyrillic sr-sp 0x0C1A 3098
Serbian - Latin sr-sp 0x081A 2074
Setsuana tn 0x0432 1074
Slovenian sl 0x0424 1060
Slovak sk 0x041B 1051
Sorbian sb 0x042E 1070
Spanish - Spain es-es 0x0C0A 1034
Spanish - Argentina es-ar 0x2C0A 11274
Spanish - Bolivia es-bo 0x400A 16394
Spanish - Chile es-cl 0x340A 13322
Spanish - Colombia es-co 0x240A 9226
Spanish - Costa Rica es-cr 0x140A 5130
Spanish - Dominican Republic es-do 0x1C0A 7178
Spanish - Ecuador es-ec 0x300A 12298
Spanish - Guatemala es-gt 0x100A 4106
Spanish - Honduras es-hn 0x480A 18442
Spanish - Mexico es-mx 0x080A 2058
Spanish - Nicaragua es-ni 0x4C0A 19466
Spanish - Panama es-pa 0x180A 6154
Spanish - Peru es-pe 0x280A 10250
Spanish - Puerto Rico es-pr 0x500A 20490
Spanish - Paraguay es-py 0x3C0A 15370
Spanish - El Salvador es-sv 0x440A 17418
Spanish - Uruguay es-uy 0x380A 14346
Spanish - Venezuela es-ve 0x200A 8202
Sutu sx 0x0430 1072
Swahili sw 0x0441 1089
Swedish - Sweden sv-se 0x041D 1053
Swedish - Finland sv-fi 0x081D 2077
Tamil ta 0x0449 1097
Tatar tt 0X0444 1092
Thai th 0x041E 1054
Turkish tr 0x041F 1055
Tsonga ts 0x0431 1073
Ukrainian uk 0x0422 1058
Urdu ur 0x0420 1056
Uzbek - Cyrillic uz-uz 0x0843 2115
Uzbek - Latin uz-uz 0x0443 1091
Vietnamese vi 0x042A 1066
Xhosa xh 0x0434 1076
Yiddish yi 0x043D 1085
Zulu zu 0x0435 1077

예를 들어 현재 시스템의 로케일 설정을 알고 싶다면 다음과 같이 코딩하면 된다.

Response.Write "<font size=-1>" & GetLocale() & "</font><br>"

필자도 그렇고, 대부분 한글 Windows를 사용하고 있을 것이므로 특별히 설정을 바꾸거나 하지 않았다면 한국(Korean)을 뜻하는 1042라는 값이 리턴될 것이다.

반대로 현재 시스템의 로케일을 영국(English - United Kingdom)으로 바꿔서 설정하고 싶다면 다음과 같이 코딩하면 된다.

orgLocID = SetLocale("en-gb")

또는...

orgLocID = SetLocale(2057)

이 코드에서 SetLocale() 함수의 리턴값을 저장하는 orgLocID 변수에는 기존의 로케일 ID 값이 10 진수로 저장된다. 따라서 잠시 로케일을 변경했다가 다시 원래의 로케일로 돌아가야 하는 상황이라면 이 리턴값을 사용하면 된다.

그러나, 사실 GetLocale() 함수와 SetLocale() 함수를 사용하지 않더라도 로케일 ID 값을 얻거나 재설정할 수 있는 방법이 전혀 없는 것은 아니다. ASP에서 제공해주는 내부 객체들 중 Session 객체를 살펴보면 LCID라는 프로퍼티가 있는데, 이 프로퍼티 역시 로케일 ID 값을 얻거나 재설정하는데 사용된다. 다음 코드는 IIS 5.0 설명서에서 발췌한 Session.LCID 예제 코드다.

<%

  Session.LCID = 2057

  Dim curNumb

  curNumb = FormatCurrency(125)
  Response.Write (curNumb)

%>

이 코드는 '£125.00'라는 문자열을 리턴해준다. 즉, 로케일 ID 값이 2057인 영국의 로케일 설정이 적용되는 것이다. 이렇게 꼭 GetLocale() 함수와 SetLocale() 함수를 사용하지 않더라도 로케일 관련 설정을 할 수 있다. 다만, GetLocale() 함수 및 SetLocale() 함수와 Session.LCID 프로퍼티는 두 가지 부분에서 큰 차이점을 가지고 있다.

첫 번째 차이점은 Session.LCID 프로퍼티는 ASP 프로그램에서만 사용할 수 있는 반면, GetLocale() 함수와 SetLocale() 함수는 VBScript를 호스팅하는 프로그램에서는 어디서든지 사용 가능하다는 점이다. 가령, 클라이언트측 VBScript 작성시에는 GetLocale() 함수와 SetLocale() 함수만 사용할 수 있다.

두 번째 차이점은 설정된 로케일의 적용 범위다. 즉, Session.LCID 프로퍼티를 사용해서 설정된 로케일은 다름 아닌 Session 객체에 포함된 프로퍼티답게 ASP의 Session 범위 전체에 걸쳐서 설정이 유지된다. 그 반면, GetLocale() 함수와 SetLocale() 함수로 설정된 로케일은 그 페이지가 끝나면 다시 시스템 기본 로케일 설정으로 돌아오는 것이다.

Timer() 함수

이전 버전의 VBScript를 사용하면서 의례 경험하게 되는 몇 가지 곤란한 상황들 중 한 가지 경우는, 바로 특정 루틴이 실행될 때 경과한 시간을 체크할 수 있는 방법이 전혀 없었다는 점이다. 기껏해야 Second() 함수를 사용해서 경과한 시간을 초 단위로 구하는 것이 고작이었다. 그러나, VBScript 5.X 버전에서부터는 Timer() 함수가 지원됨으로 해서 이런 상황이 다소나마 개선되었다.

이 Timer() 함수는 매일 오전 12시 00분 00초를 기준으로 현재까지 지나간 시간을 1/100초까지 리턴해준다. 재미있는 것은 단순히 Timer() 함수를 호출해서 리턴값을 구하는 경우에는 방금 얘기한 것처럼 1/100초 수준의 정밀도로 결과값을 리턴해주지만, Timer() 함수의 리턴값끼리 더하거나 빼는 등의 계산을 할 경우에는 정밀도가 소숫점 6~7자리까지로 늘어난다는 점이다. 다음은 임의의 For ... Next 문을 실행하는 데 경과한 시간을 구하는 코드이다.

<%

  Dim sTime, eTime
  Dim i, j

  
  sTime = Timer()      '** 작업 시작 시간
  For i = 0 To 100000
    j = j + i * 2
  Next
  eTime = Timer()      '** 작업 종료 시간

  Response.Write "<font size=-1>1. 변수 j 의 최종 결과값은 " & j & " 입니다.</font><br>"
  Response.Write "<font size=-1>2. For ... Next 문이 루프를 수행하는데 걸린 총 시간은 " & _
                 (eTime - sTime) & " 초 입니다.</font><br>"

%>

펜티엄 4인 필자의 노트북에서는 이 코드의 루프를 수행하는데 0.109375초가 걸렸다. 물론, 이러한 값들이 어떤 절대적인 의미를 지닌다고 말하기는 힘들 것이다. 더군다나 Timer() 함수의 리턴값은 매일 초기화되는 값이므로 그 사용에 주의하도록 한다.

GetRef() 함수

엄밀히 말해서 이 GetRef() 함수는 클라이언트측 VBScript에서만 사용 가능한 함수다. 그럼에도 불구하고 이렇게 소개를 하는 것은 그 예상 밖의 강력함 때문이다. GetRef() 함수는 HTML 상의 각종 이벤트들, 예를 들면 OnLoad 이벤트나 OnClick 이벤트 등이 발생하는 경우 실행될 VBScript 프로시저를 지정할 때 그 바인딩을 위해서 사용된다. 다음은 Windows Script V5.6 온라인 설명서에서 발췌한 예제 코드이다.

<SCRIPT Language="VBScript">
Function GetRefTest()
  Dim Splash
  Splash = "GetRefTest 버전 1.0"   & vbCrLf
  Splash = Splash & Chr(169) & " YourCompany 1999 "
  MsgBox Splash
End Function

Set Window.Onload = GetRef("GetRefTest")
</SCRIPT>

이 코드는 GetRefTest()라는 이름의 프로시저를 선언하고 이를 OnLoad 이벤트에 바인딩시킨다. Set 문의 사용에 유의하기 바란다. 결과적으로, OnLoad 이벤트가 발생하면 GetRefTest() 프로시저가 실행된다. GetRef() 함수는 DHTML 객체 모델에 존재하는 모든 이벤트를 대상으로 사용 가능할 뿐더러, 하나의 이벤트에 대해서 여러 번 다시 바인딩하는 것을 허용한다. 따라서 이런 특성을 JScript의 교차 언어 지원 특성과 같이 사용할 경우 <body onload="..."> 와 같은 패턴에 의한 이벤트 헨들러 바인딩과는 달리 매우 강력한 클라이언트측 프로그래밍이 가능해진다. 즉, 페이지를 새로고침 하지 않고서도 VBScript 코딩에 의해서 동일한 이벤트의 이벤트 헨들러를 한 페이지 내에서 다른 프로시저로 변경할 수 있는 것이다.

정정: GetRef() 함수를 사용해서 동적으로 이벤트 핸들러를 설정하는 방식을 설명하고 있는 이 문단에는 다소 부적절한 내용들이 포함되어 있다. 익히 알고 있는 것처럼, 이벤트 헨들러를 동적으로 설정하는 작업은 반드시 GetRef() 함수를 사용하지 않더라도, DOM에서 제공되는 attachEvent()/detachEvent() 함수나 addEventListener()/removeEventListener() 함수를 사용하면 훨씬 더 수월하게 처리할 수 있다. 이는 본문을 작성한 2000년대 초반 당시, 필자의 지식이 부족했기에 벌어진 일이다. GetRef() 함수는 함수 자체에 대한 참조를 반환해준다는 정도로만 이해하면 무리가 없을 것이다.

콜론(:) 기호

사실 콜론(:) 기호는 VBScript 5.X 버전에서 처음 소개된 기능은 아니다. 다만, 의외로 그 기능을 모르시는 분들이 많이 계셔서 여기 소개한다. 여러분은 이미 밑줄(_) 기호에 대해서 잘 알고 계실 것이다. 밑줄 기호는 한 줄에 쓰기에는 너무 긴 내용을 여러 줄로 나누어 쓸 수 있도록 해준다. ASP 프로그래밍에서는 다음 코드와 같이 주로 SQL 문을 구성할 때 사용하는 경우가 많다.

<%

  ...
  Dim strSQL
  
  strSQL = "SELECT Col01, Col02, Col03 " & _
           "FROM   Table01 " & _
           "WHERE  Col01 > 1000"
  ...

%>

반면 콜론 기호는 이와 반대로 여러 줄에 나뉜 코드를 한 줄에 쓸 수 있도록 해준다. 이런저런 설명을 하는 것 보다는 바로 코드를 살펴보는 것이 이해가 더 빠를 것이다. 다음의 코드에서 사용된 두 가지 형태의 코딩 패턴은 서로 100% 동일한 것이다.

<%

  Dim i, j, k, z
  
  '** 일반적인 형태의 코드 라인
  i = 1
  j = 3
  k = 5
  z = i + j + k
  Response.Write "<font size=-1>" & z & "</font><br>"
  
  '** 모든 코드를 한 줄에 쓴 형태의 코드 라인
  i = 1: j = 3: k = 5: z = i + j + k
  Response.Write "<font size=-1>" & z & "</font><br>"

%>

이런 기능은 관점에 따라서는 그다지 중요한 기능이 아닐 수도 있다. 그러나 필자처럼 코드 정리를 열심히 하는 습관을 갖고 있는 분들은 꽤나 유용하게 사용할 수 있을 것이라고 생각한다.