본문 바로가기

출판물/C# .NET 4 Platform

[C# .NET4] 부록(2/5): COM과 .NET 상호운용성

부록 A - COM.NET 상호운용성

- 목차

이 문서에 대해

.NET 상호운용성의 유효 범위

.NET에서 COM Interop을 사용하는 간단한 예제

.NET Interop 어셈블리 살펴보기

런타임 호출 가능 래퍼

COM IDL의 역할

타입 라이브러리를 이용해 Interop 어셈블리 만들기

좀 더 복잡한 COM 서버 만들기

Interop 어셈블리에 대하여

COM에서 .NET으로의 상호운용성

CCW의 역할

.NET 클래스 인터페이스의 역할

.NET 타입 만들기

타입 라이브러리 생성과 .NET 타입 등록

생성된 타입 정보 확인하기

비주얼 베이직 6.0 테스트 클라이언트 만들기



.NET Interop 어셈블리 살펴보기

앞의 예제에서 볼 수 있듯이 Visual Studio 2008의 참조 추가 대화상자를 통해 COM 서버의 참조를 추가하면 IDEInterop.이라는 접두어가 붙은 새 .NET 어셈블리를 생성한다(예를 들어 Interop. SimpleComServer.dll). 우리가 직접 생성하는 어셈블리와 동일하게 interop 어셈블리도 메타데이터와 어셈블리 메니페스트를 포함하며 경우에 따라서는 CIL 코드를 포함하기도 한다. 또한보통어셈블리와 같이 interop 어셈블리들은 전용 어셈블리로 배포하여(예를 들어 클라이언트 어셈블리가 설치된 디렉토리 내에) 사용하거나 강력한 이름을 부여하여 GAC에 설치해 사용할 수도 있다.

interop 어셈블리들은 원본 COM 타입에 대한 정보를 기술하는 .NET 메타데이터를 담아 놓은 파일일 뿐 그 이상 그 이하도 아니다. 대부분의 경우 interop 어셈블리들은 자신의 메서드를 위한 CIL 코드는 갖고 있지 않고 실제 모든 일은 COM 서버 자체가 한다. interop 어셈블리가 실행 가능한 CIL 코드를 포함하는 유일한 경우는 COM 서버에 포함된 COM 타입이 이벤트를 발생시킬 수 있는 경우다. 이런 경우, CLRCOMConnection Point로부터 받은 이벤트 처리 작동을 .NET 대리자로 번역하는 용도로 이 CIL 코드를 사용한다.처음 보는 사람들은 interop 어셈블리들은 구현 코드도 별로 없는 쓸모없는 것이 아닌가라고 생각할 수 있다. 하지만 interop 어셈블리 안에 포함된 메타데이터는 매우 중요하며, CLR은 런타임 시에 이 정보를 이용해 런타임 프록시(RCW: Runtime Callable Wrapper)를 생성하고 이는 .NET 응용 프로그램과 이 응용 프로그램이 통신하고자 하는 COM 객체 사이를 연결시켜주는 다리 역할을 한다.

RCW에 대한 내용은 다음에 나오는 절들에서 더 자세히 알아볼 것이다. 우선 지금은 ildasm.exe를 이용해 Interop.SimpleComServer.dll을 열어보자.

 

그림 A-5  Interop.SimpleComServer.dll interop 어셈블리의 내부

 

위에서 볼 수 있듯이 VB6 프로젝트는 하나의 COM 클래스(ComCalc)만을 정의하고 있지만 interop 어셈블리는 ‘3의 타입을 포함하는 것을 볼 수 있다. 이를 VS 2008 개체 브라우저에서 확인해봐도 동일하다(그림 A6 참조).

 

그림 A-6  , 하나의 COM 타입에서 어떻게 세 개의 .NET 타입이 나오게 되는 것일까?

 

쉽게 말해 각 COM 클래스는 3개의 .NET 타입으로 표현된다. 우선 첫째로 COM 타입과 동일한 이름의 .NET 타입이 있다(이 예제의 경우엔 ComCalc). 그 다음으로 Class 접미사를 갖는 .NET 타입이 있다(ComCalcClass). 이 두 타입들은 하나의 COM 타입이 여러 개의 사용자 정의 인터페이스를 구현하는 경우에 유용하다. 왜냐하면 Class가 붙은 타입들은 해당 COM 타입이 구현하는 각 인터페이스의 모든 멤버 함수를 노출해주기 때문이다. 따라서 .NET 프로그래머의 입장에서는 특정 인터페이스의 메서드를 호출하기 위해서 그 인터페이스의 참조를 얻어올 필요가 없어지는 것이다. 비록 앞 예제의 ComCalc가 여러 개의 사용자 정의 인터페이스를 구현하고 있지는 않지만 ComCalcClass를 통해 Add()Subtract() 메서드를 (ComCalc 객체를 통하는 대신)다음의 예제처럼 호출할 수 있다.

 

static void Main(string[] args)

{

  Console.WriteLine("***** The .NET COM Client App *****");

 

  // Class 접미사가 붙은 타입을 사용

  ComCalcClass comObj = new ComCalcClass();

  Console.WriteLine("COM server says 10 + 832 is {0}",

    comObj.Add(10, 832));

  Console.ReadLine();

}

 

마지막으로 interop 어셈블리들은 COM 서버 내에 정의되어 있는 모든 COM 인터페이스에 대한 .NET 인터페이스의 정의를 담는다. 앞의 예제의 경우 _ComCalc라는 .NET 인터페이스가 포함되어 있는 것을 볼 수 있다. VB6 COM에 대해 잘 알지 못하는 개발자라면 이것이 이상하게 보일 것이다. SimpleComServer 프로젝트에서 (_ComCalc 인터페이스는커녕) 어떠한 인터페이스도 직접 생성한 적이 없는데 이건 어디서 나타난 것인가 하고 말이다. 이런 밑줄 문자가 앞에 붙은 인터페이스들의 역할은 부록 A의 후반부에 가면 명확히 알 수 있을 것이다. 일단 지금은 COM의 인터페이스를 이용해 해당 기능을 호출하고 싶은 경우엔 다음처럼 Add()Subtract()를 호출할 수 있는 방법이 있다고만 알아두자.

 

static void Main(string[] args)

{

  Console.WriteLine("***** The .NET COM Client App *****");

 

  // 숨겨진 인터페이스를 얻어온다.

  ComCalc itfComInterface = null;

  ComCalcClass comObj = new ComCalcClass();

  itfComInterface = (_ComCalc)comObj;

 

  Console.WriteLine("COM server says 10 + 832 is {0}",

    itfComInterface.Add(10, 832));

  Console.ReadLine();

}

 

지금은 일단 Class 접미사가 붙은 클래스나 밑줄 문자가 앞에 붙은 인터페이스를 이용해 메서드를 호출해야 하는 경우는 드물다고만 알아두자. 그러나 만드는 .NET 응용 프로그램이 복잡해지고 COM 타입을 사용하는 방식도 복잡해지는 경우 이런 것들을 알아두면 매우 유용하다.

 

소스코드

CSharpComClient 프로젝트는 Appendix A 서브디렉토리에 있다.

 

 

런타임 호출 가능 래퍼

앞서 언급한 대로 런타임 시 CLR.NET interop 어셈블리에 포함된 메타데이터를 이용해 프록시를 생성하고 이는 .NET에서 COM으로의 통신을 위해 사용된다. 여기서 말하는 프록시는 바로 런타임 호출 가능 래퍼(RCW)로 이는 실제 COM 클래스와(공식적으로 coclass라고 부른다) .NET 클래스를 이어주는 다리 역할을 한다. .NET 응용 프로그램이 사용하는 모든 coclass는 이에 해당하는 RCW가 있어야 한다. 따라서 한 .NET 응용 프로그램에서 세 개의 COM coclass를 사용한다면 .NET에서 호출하는 각 COM에 대한 세 개의 RCW가 생성된다. 그림 A7에서 이에 대한 큰 그림을 볼 수 있다.

 

알아두기

한 개의 COM 객체에 대한 RCW는 딱 한 개만 존재한다. 이는 .NET 클라이언트가 이 COM 객체로부터 몇 개의 인터페이스를 얻어 사용한다 해도 동일하다(여러 개의 인터페이스를 지원하는 VB6 COM 객체는 부록 A 후반부에서 다룬다). 이렇게 함으로써 COM 객체의 올바른 관리를 할 수 있다(참조 카운트 관리와 더불어서).

 

다시 말하지만 여기서 좋은 점은 바로 RCWCLR이 이를 필요로 할 때만 자동 생성된다는 것이다. 또 다른 좋은 점은 COM 서버는 아무런 수정 없이 .NET 언어에서 사용 가능하다는 것이다. 이 둘 사이를 연결시켜주는 RCW가 내부적으로 필요한 모든 일을 처리한다. 이것이 정확히 어떻게 작동하는지 보기 위해 RCW가 하는 핵심 임무를 정식으로 나열해보자.

 

그림 A-7  RCW.NET 호출자와 COM 객체 사이에 끼어 작동한다.

 

 

COM 타입을 .NET 타입으로 노출시켜주는 RCW

RCWCOM 데이터 타입을 .NET에서 대응되는 타입으로 변환을 하는 일을(그리고 이와 반대의 경우도) 담당한다. 간단한 예로 다음과 같이 정의된 VB6 함수가 있다고 가정해보자.

 

' VB6 COM 메서드 정의

Public Sub DisplayThisString(ByVal s as String)

 

interop 어셈블리는 이 메서드의 매개변수를 .NETSystem.String 타입으로 정의한다.

 

' COM 메서드의 C#으로의 변환

public void DisplayThisString(string s)

 

이 메서드를 .NET 코드에서 호출하면 RCWSystem.String 타입의 매개변수를 VB6String 타입으로(이미 알겠지만 정확히는 COMBSTR) 변환한다. 아마도 짐작하고 있을 것이라 생각되는데, 모든 COM 데이터 타입은 이에 대응되는 .NET 타입이 있다. A1을 보면 COM IDL(interface definition language)상의 데이터 타입과 이와 연관된 .NET System 타입과의 관계를 볼 수 있다. 또한 이에 대응되는 C# 키워드 또한 볼 수 있다(적용 가능한 것들에 한해서).

 

A-1. COM 타입과 .NET 타입의 대응 관계

COM IDL Data Type

System Types

C# Keyword

wchar_t, short

System.Int16

short

Long,int

System.Int32 int

Int

Hyper

System.Int64

long

unsigned char, byte

System.Byte byte

Byte

single

System.Single

-

Double

System.Double

double

VARIANT_BOOL

System.Boolean

bool

BSTR

System.String

String

VARIANT

System.Object

object

DECIMAL

System.Decimal

-

DATE

System.DateTime

-

GUID

System.Guid

-

CURRENCY

System.Decimal

-

IUnknown

System.Object

object

IDispatch

System.Object

object

 

 

Coclass의 참조 카운트를 관리하는 RCW

RCW의 또 다른 중요한 임무는 바로 COM 객체의 참조 카운트를 관리하는 것이다. COM을 다뤄본 경험이 있으면 알 수 있겠지만 COM의 참조 카운트 관리 방식은 coclass와 이를 이용하는 클라이언트 쌍방의 노력이 필요한 작업으로 이는 AddRef()Release() 함수 호출을 통해 이뤄진다. COM 객체는 자신을 참조하는 객체가 없다고 판단되면 자신을 스스로 메모리에서 해제한다.

하지만 .NET 타입들은 COM의 참조 카운트 관리 방식을 사용하지 않으므로 .NET 클라이언트가 사용하는 COM 타입에 Release()를 호출하도록 강요해서는 안 된다. 이 상황에서 양쪽을 모두 만족시키기 위해서 RCW.NET 응용 프로그램이 참조하는 모든 인터페이스의 참조를 캐시에 저장하고 해당 타입이 .NET 응용 프로그램에서 더 이상 사용되지 않을 때 최종 해제를 한다. 여기서 말하고자 하는 결론은 VB6와 비슷하게 .NET 클라이언트에서는 AddRef(), Release(), QueryInterface()를 직접 호출할 일이 없다는 것이다.

 

알아두기

.NET 응용 프로그램에서 COM 객체의 참조 카운트를 직접 다루고 싶다면 System.Runtime.InteropServicesMarshal 타입을 사용하면 된다. 이 클래스에 정의된 몇 개의 정적 메서드를 이용하면 COM 객체의 수명 관리를 직접 할 수 있다. 대부분의 응용 프로그램에서 Marshal 클래스를 이용해야 하는 경우는 거의 없겠지만 이에 대한 자세한 내용을 원하는 독자는 .NET Framework 3.5 SDK 문서를 참조하라.

 

 

COM의 필수 기반 인터페이스를 숨겨주는 RCW

마지막으로 볼 RCW의 핵심 기능은 바로 COM의 여러 필수 인터페이스에 대한 처리를 자동으로 해주는 것이다. COM 객체를 사용하는 .NET 응용 프로그램 입장에서 COM이 아닌 실제 .NET 타입을 사용하는 것 같은 환상을 주기 위해 RCWCOM의 필수 인터페이스와 관련된 내용을 숨긴다.

예를 들어 IConnectionPointContainer를 구현하는(그리고 IConnectionPoint를 구현하는 추가 객체를 갖는) COM 클래스를 만들면 해당 COM 객체의 coclassCOM 클라이언트에게 이벤트를 발생시킬 수 있다. VB6는 이에 대한 내용을 EventRaiseEvent 키워드를 이용해 이 모든 과정을 숨긴다. 이와 같은 이치로 RCW 또한 이런 COM스러운 것들을 .NET 클라이언트가 보지 못하게 숨겨준다. A2RCW가 숨기는 COM 인터페이스의 역할을 간략히 나열하고 있다.

 

A-2. 숨겨진 COM 인터페이스

숨겨진 COM 인터페이스

실제 의미

IConnectionPointContainer

IConnectionPoint

Coclass가 관심 있는 클라이언트에게 이벤트를 전달 할 수 있게 해준다. VB6는 이에 대한 기본 구현을 제공한다.

IDispatch

IProvideClassInfo

Coclass늦은 바인딩이 용이하게 해준다. VB6를 이용해 COM 타입을 생성 하는 경우 이들 인터페이스에 대한 기본 구현은 자동으로 제공 된다.

IErrorInfo

ISupportErrorInfo

ICreateErrorInfo

이 인터페이스들은 COM 객체와 COM 클라이언트가 COM 에러에 대응 할 수 있게 해준다.

IUnknown

COM의 제 일인자 인터페이스. COM 객체의 참조 카운트를 관리 하며 coclass의 다른 인터페이스에 접근 가능 하게 해준다.

 

 

COM IDL의 역할

여기까지 잘 따라온 독자는 interop 어셈블리와 RCW의 역할을 잘 이해했으리라 믿는다. COM에서 .NET으로의 변환 과정을 자세히 알아보기 전에 COM IDL의 세부 내용을 좀 더 알아볼 필요가 있다. 여기서 이해하고 넘어가야 할 부분은 부록 ACOM IDL에 대한 설명서가 아니라는 점이다. 그렇지만 interop 계층에 대한 좀 더 명확한 이해를 돕기 위해 IDL 구조 몇 가지에 대해서만 알아보자.

모든 .NET 어셈블리는 메타데이터를 포함한다. 좀 더 정확하게 설명하자면 메타데이터는 .NET 어셈블리의 각 특성과 모든 면에 대한 정보를 기술하는 데 사용된다. 여기에는 내부에 포함되어 있는 타입(멤버들과 기본 클래스 등), 어셈블리 버전, 그리고 추가적인 어셈블리 수준의 정보들(강력한 이름, 지역 로케일(culture))이 포함된다.

.NET의 메타데이터는 여러 가지 방면에서 기존의 COM 서버를 기술하기 위해 사용됐던 예전 메타데이터의 큰형 격이라고 할 수 있다. 기존의 ActiveX COM 서버(*.dll이나 *.exe)들은 자신의 내부 타입에 대한 정보를 타입 라이브러리를 이용해 저장했고 이는 *.tlb 파일로 별도 저장하거나 COM 서버의 내부 리소스에 포함(VB6에서는 이 방법이 기본)하기도 했다. COM의 타입 라이브러리는 보통 Interface Definition Language라는 메타데이터 언어와 midl.exe(Microsoft IDL 컴파일러)를 이용해 생성했다.VB6는 이 타입 라이브러리와 IDL의 내용을 개발자의 눈에 띄지 않도록 잘 숨겨준다. 실제로 많은 숙련된 VB COM 프로그래머들이 IDL과 그 문법을 완전히 무시한 채로도 행복하게 잘 살아가고 있기도 하다. 어쨌든 VB6를 이용해 ActiveX 프로젝트를 컴파일하면 VB6는 내부적으로 타입 라이브러리를 자동으로 생성하고 이를 *.dll이나 *.exe COM 서버에 삽입해준다. 더 나아가 VB6는 해당 타입 라이브러리를 HKEY_CLASSES_ROOT\TypeLib 시스템 레지스트리에 자동으로 등록시켜준다(그림 A8 참조).

많은 IDE가 이 타입 라이브러리를 참조한다. 한 가지 예로 VB6에서 Project → References 메뉴 항목을 선택하면 IDEHKCR\TypeLib를 참조하여 그림 A9에서처럼 등록된 모든 타입 라이브러리에 대한 정보를 불러온다.

 

그림 A-8  HKCR\TypeLib에 해당 컴퓨터에 모든 타입 라이브러리를 갖고 있다.

 

그림 A-9  VB6에서 COM 타입 정보 참조하기

 

이와 동일하게 VB6 개체 찾아보기 창(Object Browser)을 열어보면 VB6는 타입 정보를 읽어와 사용자에게 보기 편한 GUICOM 서버의 내용을 그림 A10에서처럼 보여준다.

 

그림 A-10  VB6 개체 찾아보기 창을 이용해 타입 라이브러리 보기

 

VB COM 서버의 자동 생성된 IDL

VB6 개체 찾아보기 창을 이용하면 한 타입 라이브러리 포함된 모든 COM 타입에 대한 정보를 볼 수 있으나 OLE View 유틸리티(oleview.exe)를 이용하면 해당 타입 라이브러리를 만드는 데 사용된 IDL 구문 또한 볼 수 있다. Visual Basic 6.0을 설치한 상태이면 시작모든 프로그램 → Microsoft Visual Studio 6.0 → Microsoft Visual Studio 6.0 Tools에서 OLE View를 실행시킬 수 있다. 실행한 후에 그림 A11처럼 SimpleComServer 서버를 왼쪽 트리뷰의 타입 라이브러리 항목 아래에서 찾아 선택한다.

 

그림 A-11  OLE/COM Object Viewer를 이용해 SimpleComServer 찾아보기

 

타입 라이브러리 아이콘을 더블클릭해보면 새창이 뜨면서 VB6 컴파일러가 생성한 타입 라이브러리를 구성하는 모든 IDL 토큰에 대한 내용을 보여준다. 아래는 관련된(그리고 약간 수정된) IDL 내용이다([uuid] 값은 다를 수 있다).

 

[uuid(8AED93CB-7832-4699-A2FC-CAE08693E720), version(1.0)]

library SimpleComServer

{

importlib("stdole2.tlb");

interface _ComCalc;

[odl, uuid(5844CD28-2075-4E77-B619-9B65AA0761A3), version(1.0),

hidden, dual, nonextensible, oleautomation]

interface _ComCalc : IDispatch {

[id(0x60030000)]

HRESULT Add([in] short x, [in] short y, [out, retval] short* );

[id(0x60030001)]

HRESULT Subtract([in] shortx, [in] short y, [out, retval] short* );

};

[uuid(012B1485-6834-47FF-8E53-3090FE85050C), version(1.0)]

coclass ComCalc {

[default] interface _ComCalc;

};

};

 

IDL 어트리뷰트

IDL을 분석하기에 앞서 IDL 구문 속 대괄호([…]) 안의 코드 블록에 주목해보자. 이 대괄호 사이에는 쉼표로 구분되는 IDL 키워드 목록이 나오는데 이는바로 다음에 오는 것의 모호한 점을 없애주는 역할을 한다(여기서바로 다음에 오는 것이란 이 코드 블록 다음에 오는 또는 바로 밑에 있는 항목을 말한다. , 바로 다음 항목에 대한 좀 더 자세한 설명을 해준다). 이 코드 블록들이 바로 IDL 어트리뷰트란 것인데 .NET의 어트리뷰트와 같은 역할을 한다(, 무언가를 설명해준다는 점). 주요 IDL 어트리뷰트 중 하나로 [uuid]가 있는데 이는 주어진 COM 타입의 GUID를 나타낸다. 아마도 알고 있겠지만 COM의 거의 모든 것들에 GUID가 부여되는데(인터페이스, COM 클래스, 타입 라이브러리 등) 이는 해당 항목의 고유 식별자로 이용된다.

 

IDL 라이브러리 구문

맨 위에는 IDL library 키워드를 이용한 COM 라이브러리 구문이 있다. 이 라이브러리 구문 안에는 모든 COM 클래스, 인터페이스 그리고 혹시라도 있을 열거자와 사용자 정의 타입에 대한 정보가 포함되어 있다. SimpleComServer의 경우 타입 라이브러리에는 정확히 한 개의 COM 클래스(ComCalc)가 있고 이는 coclass(, COM class) 키워드를 이용해 표시한다.

 

[default] 인터페이스의 역할

COM의 법칙에 의하면 COM 클래스와 COM 클라이언트가 서로 통신을 할 수 있는 유일한 방법은 인터페이스 참조를(객체에 대한 참조가 아닌) 사용하는 방법뿐이다. 만약 C++로 클라이언트를 만든 경우라면 QueryInterface()로 주어진 인터페이스를 얻고 이를 다 사용하면 Release()를 이용해 해제하는 과정을 잘 알고 있을 것이다. 하지만 VB6를 이용해 클라이언트를 만든 경우라면 COM 클래스의 기본 인터페이스(default interface)를 이용해 COM 서버를 이용하게 된다.

VB6를 이용해 COM 서버를 만들면 *.cls 파일 내에 선언된 모든 public 멤버들(예를 들어 Add() 함수) COM 클래스의기본 인터페이스의 멤버로 포함된다. ComCalc의 클래스 정의를 들여다보면 _ComCalc가 기본 인터페이스로 선언되어 있는 것을 볼 수 있다.

 

[uuid(012B1485-6834-47FF-8E53-3090FE85050C), version(1.0)]

coclass ComCalc {

  [default] interface _ComCalc;

};

 

궁금해하는 독자가 있을 것 같아 설명하자면 VB6가 생성하는 기본 인터페이스의 이름은 항상 _클래스의 이름(여기서 밑줄 문자는 숨겨진 인터페이스임을 나타내기 위해 사용된다)이다. 따라서 Car라는 이름의 클래스를 선언했다면 이 클래스의 기본 인터페이스는 _Car가 된다. 그리고 DataConnector라는 클래스가 있다면 _DataConnector가 된다. VB6에서 작업하는 경우 기본 인터페이스는 겉으로는 보이지 않는다. 하지만 VB6에서 다음과 같은 코드를 작성하면

 

' VB 6.0 COM 클라이언트 코드

Dim c As ComCalc

Set c = New ComCalc ' [default] _ComCalc 인터페이스가 자동으로 반환됨

 

VB 런타임은 자동으로 객체의 기본 인터페이스(타입 라이브러리에 지정된 대로)를 얻어와 클라이언트에 전달한다. VB는 항상 COM 클래스의 기본 인터페이스만을 반환하기 때문에 이를 실제 객체에 대한 참조인 것처럼 사용해도 된다. 하지만 이는 코드 구문상에서 VB6가 만들어내는 환상일 뿐, 실제 COM에서는 객체에 대해 직접 참조하는 경우는 절대로 없다. 꼭 인터페이스 참조를 통해서만 접근 가능하며, 이는 해당 인터페이스가 기본 인터페이스라고 할지라도 동일하다.

 

IDispatch의 역할

IDL에 나와 있는 _ComCalc 기본 인터페이스에 대한 정의를 보면 이 인터페이스가 IDispatch라는 COM의 표준 인터페이스를 상속하는 것을 알 수 있다. IDispatch의 용도를 다 설명하려면 이 부록의 범위를 벗어나므로 일단은 Active Server Page와 같은 웹 환경 혹은 늦은 바인딩을 필요로 하는 환경에서 COM 객체에 접근 가능하도록 해주는 인터페이스라고만 알아두자.

 

IDL 매개변수 어트리뷰트

IDL의 내용 중 마지막으로 알아둬야 할 부분이 바로 VB6의 타입들이 내부적으로 어떻게 표현되는가이다. VB6에서는 ByVal 키워드를 사용하지 않는 한 모든 매개변수를 참조로 전달한다. 이는 IDL [in] 어트리뷰트로 표기한다. 그리고 함수의 반환 값에는 [out, retval] 어트리뷰트를 추가해준다. 따라서 다음의 VB6 함수는

 

' VB6 함수

Public Function Add(ByVal x as Integer, ByVal y as Integer) as Integer

  Add = x + y

End Function

 

IDL에서는 다음과 같이 표현된다.

 

HRESULT Add([in] short* x, [in] short* y, [out, retval] short*);

 

반면에 매개변수에 VB6 ByVal 키워드로 표기하지 않는 경우엔 ByRef가 사용된다.

 

' 이 파라미터들은 VB6에서는 ByRef로 전달된다.

Public Function Subtract(x As Integer, y As Integer) As Integer

  Subtract = x - y

End Function

 

ByRef 매개변수들은 IDL에서는 [in, out] 어트리뷰트로 표기된다.

 

HRESULT Subtract([in, out] short x, [in, out] short y, [out, retval] short*);