상호 운용성 문제 해결(Visual Basic)

COM과 .NET Framework 관리 코드 간에 상호 운용하는 경우 다음과 같은 일반적인 문제 중 하나 이상이 발생할 수 있습니다.

Interop 마샬링

때때로 .NET Framework 포함되지 않은 데이터 형식을 사용해야 할 수 있습니다. Interop 어셈블리는 COM 개체에 대한 대부분의 작업을 처리하지만 관리되는 개체가 COM에 노출될 때 사용되는 데이터 형식을 제어해야 할 수 있습니다. 예를 들어 클래스 라이브러리의 구조체는 Visual Basic 6.0 및 이전 버전에서 만든 COM 개체로 전송된 문자열에서 관리되지 않는 형식을 지정 BStr 해야 합니다. 이러한 경우 특성을 사용하여 관리되는 형식이 MarshalAsAttribute 관리되지 않는 형식으로 노출되도록 할 수 있습니다.

Fixed-Length 문자열을 관리되지 않는 코드로 내보내기

Visual Basic 6.0 이전 버전에서는 문자열이 null 종료 문자 없이 COM 개체에 바이트 시퀀스로 내보내집니다. 다른 언어와의 호환성을 위해 Visual Basic .NET에는 문자열을 내보낼 때 종료 문자가 포함됩니다. 이 비호환성을 해결하는 가장 좋은 방법은 종료 문자가 없는 문자열을 또는 CharByte 배열로 내보내는 것입니다.

상속 계층 구조 내보내기

관리되는 클래스 계층 구조는 COM 개체로 노출될 때 평면화됩니다. 예를 들어 멤버를 사용하여 기본 클래스를 정의한 다음 COM 개체로 노출되는 파생 클래스에서 기본 클래스를 상속하는 경우 COM 개체에서 파생 클래스를 사용하는 클라이언트는 상속된 멤버를 사용할 수 없습니다. 기본 클래스 멤버는 기본 클래스의 인스턴스로만 COM 개체에서 액세스할 수 있으며 기본 클래스도 COM 개체로 만들어진 경우에만 액세스할 수 있습니다.

오버로드된 메서드

Visual Basic을 사용하여 오버로드된 메서드를 만들 수 있지만 COM에서는 지원되지 않습니다. 오버로드된 메서드를 포함하는 클래스가 COM 개체로 노출되면 오버로드된 메서드에 대해 새 메서드 이름이 생성됩니다.

예를 들어 메서드의 오버로드가 두 개 있는 클래스를 Synch 고려합니다. 클래스가 COM 개체로 노출되면 생성된 새 메서드 이름은 및 Synch_2Synch 수 있습니다.

이름을 변경하면 COM 개체의 소비자에게 두 가지 문제가 발생할 수 있습니다.

  1. 클라이언트는 생성된 메서드 이름을 기대하지 않을 수 있습니다.

  2. COM 개체로 노출되는 클래스에서 생성된 메서드 이름은 클래스 또는 기본 클래스에 새 오버로드가 추가될 때 변경될 수 있습니다. 이로 인해 버전 관리 문제가 발생할 수 있습니다.

두 문제를 모두 해결하려면 COM 개체로 노출될 개체를 개발할 때 오버로드를 사용하는 대신 각 메서드에 고유한 이름을 지정합니다.

Interop 어셈블리를 통해 COM 개체 사용

interop 어셈블리는 마치 그들이 나타내는 COM 개체에 대한 관리 코드 대체인 것처럼 사용합니다. 그러나 실제 COM 개체가 아니라 래퍼이므로 interop 어셈블리와 표준 어셈블리 사용 간에는 몇 가지 차이점이 있습니다. 이러한 차이점 영역에는 클래스 노출과 매개 변수 및 반환 값에 대한 데이터 형식이 포함됩니다.

인터페이스 및 클래스 모두로 노출되는 클래스

표준 어셈블리의 클래스와 달리 COM 클래스는 인터페이스와 COM 클래스를 나타내는 클래스로 interop 어셈블리에 노출됩니다. 인터페이스의 이름은 COM 클래스의 이름과 동일합니다. interop 클래스의 이름은 원래 COM 클래스의 이름과 동일하지만 "Class"라는 단어가 추가되었습니다. 예를 들어 COM 개체에 대한 interop 어셈블리에 대한 참조가 있는 프로젝트가 있다고 가정합니다. COM 클래스 MyComClass이름이 인 경우 IntelliSense 및 Object Browser는 라는 인터페이스와 라는 MyComClassMyComClassClass클래스를 표시합니다.

.NET Framework 클래스의 인스턴스 만들기

일반적으로 클래스 이름을 가진 문을 사용하여 New .NET Framework 클래스의 instance 만듭니다. interop 어셈블리가 나타내는 COM 클래스를 갖는 것은 인터페이스와 함께 문을 사용할 New 수 있는 한 가지 경우입니다. 문과 함께 COM 클래스를 Inherits 사용하지 않는 한 클래스와 마찬가지로 인터페이스를 사용할 수 있습니다. 다음 코드에서는 Microsoft ActiveX Data Objects 2.8 Library COM 개체에 대한 참조가 있는 프로젝트에서 개체를 만드는 Command 방법을 보여 줍니다.

Dim cmd As New ADODB.Command

그러나 COM 클래스를 파생 클래스의 기반으로 사용하는 경우 다음 코드와 같이 COM 클래스를 나타내는 interop 클래스를 사용해야 합니다.

Class DerivedCommand
    Inherits ADODB.CommandClass
End Class

참고

Interop 어셈블리는 COM 클래스를 나타내는 인터페이스를 암시적으로 구현합니다. 문을 사용하여 이러한 인터페이스를 Implements 구현하려고 하면 안 되며 오류가 발생합니다.

매개 변수 및 반환 값에 대한 데이터 형식

표준 어셈블리의 멤버와 달리 interop 어셈블리 멤버에는 원래 개체 선언에 사용된 것과 다른 데이터 형식이 있을 수 있습니다. interop 어셈블리는 COM 형식을 호환되는 공용 언어 런타임 형식으로 암시적으로 변환하지만 런타임 오류를 방지하기 위해 양쪽에서 사용하는 데이터 형식에 주의해야 합니다. 예를 들어 Visual Basic 6.0 및 이전 버전에서 만든 COM 개체에서 형식 Integer 의 값은 .NET Framework 해당하는 형식으로 Short가정합니다. 개체 브라우저를 사용하여 가져온 멤버의 특성을 검사한 후 사용하는 것이 좋습니다.

모듈 수준 COM 메서드

대부분의 COM 개체는 키워드(keyword) 사용하여 New COM 클래스의 instance 만든 다음 개체의 메서드를 호출하는 데 사용됩니다. 이 규칙의 한 가지 예외는 또는 GlobalMultiUse COM 클래스를 포함하는 AppObj COM 개체와 관련이 있습니다. 이러한 클래스는 Visual Basic .NET 클래스의 모듈 수준 메서드와 유사합니다. Visual Basic 6.0 및 이전 버전은 메서드 중 하나를 처음 호출할 때 이러한 개체의 인스턴스를 암시적으로 만듭니다. 예를 들어 Visual Basic 6.0에서는 먼저 instance 만들지 않고 Microsoft DAO 3.6 개체 라이브러리에 대한 참조를 추가하고 메서드를 호출 DBEngine 할 수 있습니다.

Dim db As DAO.Database  
' Open the database.  
Set db = DBEngine.OpenDatabase("C:\nwind.mdb")  
' Use the database object.  

Visual Basic .NET을 사용하려면 항상 COM 개체의 인스턴스를 만들어야 메서드를 사용할 수 있습니다. Visual Basic에서 이러한 메서드를 사용하려면 원하는 클래스의 변수를 선언하고 새 키워드(keyword) 사용하여 개체 변수에 개체를 할당합니다. 클래스의 Shared instance 하나만 만들도록 하려는 경우 키워드(keyword) 사용할 수 있습니다.

' Class level variable.
Shared DBEngine As New DAO.DBEngine

Sub DAOOpenRecordset()
    Dim db As DAO.Database
    Dim rst As DAO.Recordset
    Dim fld As DAO.Field
    ' Open the database.
    db = DBEngine.OpenDatabase("C:\nwind.mdb")

    ' Open the Recordset.
    rst = db.OpenRecordset(
        "SELECT * FROM Customers WHERE Region = 'WA'",
        DAO.RecordsetTypeEnum.dbOpenForwardOnly,
        DAO.RecordsetOptionEnum.dbReadOnly)
    ' Print the values for the fields in the debug window.
    For Each fld In rst.Fields
        Debug.WriteLine(fld.Value.ToString & ";")
    Next
    Debug.WriteLine("")
    ' Close the Recordset.
    rst.Close()
End Sub

이벤트 처리기의 처리되지 않은 오류

일반적인 interop 문제 중 하나는 COM 개체에서 발생한 이벤트를 처리하는 이벤트 처리기의 오류와 관련이 있습니다. 또는 Try...Catch...Finally 문을 사용하는 On Error 오류에 대해 구체적으로 검사 않는 한 이러한 오류는 무시됩니다. 예를 들어 다음 예제는 Microsoft ActiveX Data Objects 2.8 Library COM 개체에 대한 참조가 있는 Visual Basic .NET 프로젝트의 예제입니다.

' To use this example, add a reference to the 
'     Microsoft ActiveX Data Objects 2.8 Library  
' from the COM tab of the project references page.
Dim WithEvents cn As New ADODB.Connection
Sub ADODBConnect()
    cn.ConnectionString =
    "Provider=Microsoft.Jet.OLEDB.4.0;" &
    "Data Source=C:\NWIND.MDB"
    cn.Open()
    MsgBox(cn.ConnectionString)
End Sub

Private Sub Form1_Load(ByVal sender As System.Object,
    ByVal e As System.EventArgs) Handles MyBase.Load

    ADODBConnect()
End Sub

Private Sub cn_ConnectComplete(
    ByVal pError As ADODB.Error,
    ByRef adStatus As ADODB.EventStatusEnum,
    ByVal pConnection As ADODB.Connection) Handles cn.ConnectComplete

    '  This is the event handler for the cn_ConnectComplete event raised 
    '  by the ADODB.Connection object when a database is opened.
    Dim x As Integer = 6
    Dim y As Integer = 0
    Try
        x = CInt(x / y) ' Attempt to divide by zero.
        ' This procedure would fail silently without exception handling.
    Catch ex As Exception
        MsgBox("There was an error: " & ex.Message)
    End Try
End Sub

이 예제에서는 예상대로 오류를 발생합니다. 그러나 블록 없이 동일한 예제를 Try...Catch...Finally 시도하면 문을 사용한 OnError Resume Next 것처럼 오류가 무시됩니다. 오류 처리가 없으면 0으로 나누기 작업이 자동으로 실패합니다. 이러한 오류는 처리되지 않은 예외 오류를 발생하지 않으므로 COM 개체의 이벤트를 처리하는 이벤트 처리기에서 일종의 예외 처리를 사용하는 것이 중요합니다.

COM interop 오류 이해

오류 처리 없이 interop 호출은 정보를 거의 제공하지 않는 오류를 생성하는 경우가 많습니다. 가능하면 구조적 오류 처리를 사용하여 문제가 발생할 때 문제에 대한 자세한 정보를 제공합니다. 이는 애플리케이션을 디버그할 때 특히 유용할 수 있습니다. 예:

Try
    ' Place call to COM object here.
Catch ex As Exception
    ' Display information about the failed call.
End Try

예외 개체의 내용을 검사하여 오류 설명, HRESULT 및 COM 오류의 원본과 같은 정보를 찾을 수 있습니다.

ActiveX 컨트롤 문제

Visual Basic 6.0에서 작동하는 대부분의 ActiveX 컨트롤은 Visual Basic .NET에서 문제 없이 작동합니다. 기본 예외는 컨테이너 컨트롤 또는 다른 컨트롤을 시각적으로 포함하는 컨트롤입니다. Visual Studio에서 제대로 작동하지 않는 이전 컨트롤의 몇 가지 예는 다음과 같습니다.

  • Microsoft Forms 2.0 프레임 컨트롤

  • 스핀 컨트롤이라고도 하는 Up-Down 컨트롤

  • 셰리던 탭 컨트롤

지원되지 않는 ActiveX 컨트롤 문제에 대한 몇 가지 해결 방법만 있습니다. 원래 소스 코드를 소유하는 경우 기존 컨트롤을 Visual Studio로 마이그레이션할 수 있습니다. 그렇지 않으면 업데이트된 에 대한 소프트웨어 공급업체와 검사 수 있습니다. 지원되지 않는 ActiveX 컨트롤을 대체할 NET 호환 버전의 컨트롤입니다.

Controls ByRef의 ReadOnly 속성 전달

Visual Basic .NET에서는 일부 이전 ActiveX 컨트롤 ByRef 의 속성을 매개 변수로 다른 프로시저에 전달할 ReadOnly 때 "오류 0x800A017F CTL_E_SETNOTSUPPORTED"과 같은 COM 오류가 발생하는 경우가 있습니다. Visual Basic 6.0의 유사한 프로시저 호출은 오류를 발생시키지 않으며 매개 변수는 값으로 전달된 것처럼 처리됩니다. Visual Basic .NET 오류 메시지는 속성 Set 프로시저가 없는 속성을 변경하려고 했음을 나타냅니다.

호출되는 프로시저에 액세스할 수 있는 경우 키워드(keyword) 사용하여 ByVal 속성을 허용하는 ReadOnly 매개 변수를 선언하여 이 오류를 방지할 수 있습니다. 예:

Sub ProcessParams(ByVal c As Object)
    'Use the arguments here.
End Sub

호출되는 프로시저에 대한 소스 코드에 액세스할 수 없는 경우 호출 프로시저 주위에 대괄호 집합을 추가하여 속성을 값으로 강제로 전달할 수 있습니다. 예를 들어 Microsoft ActiveX Data Objects 2.8 Library COM 개체에 대한 참조가 있는 프로젝트에서 다음을 사용할 수 있습니다.

Sub PassByVal(ByVal pError As ADODB.Error)
    ' The extra set of parentheses around the arguments
    ' forces them to be passed by value.
    ProcessParams((pError.Description))
End Sub

Interop을 노출하는 어셈블리 배포

COM 인터페이스를 노출하는 어셈블리를 배포하면 몇 가지 고유한 문제가 발생합니다. 예를 들어 별도의 애플리케이션이 동일한 COM 어셈블리를 참조할 때 잠재적인 문제가 발생합니다. 이 상황은 새 버전의 어셈블리가 설치되고 다른 애플리케이션이 여전히 이전 버전의 어셈블리를 사용하는 경우에 일반적입니다. DLL을 공유하는 어셈블리를 제거하는 경우 의도치 않게 다른 어셈블리에서 사용할 수 없게 만들 수 있습니다.

이 문제를 방지하려면 GAC(전역 어셈블리 캐시)에 공유 어셈블리를 설치하고 구성 요소에 MergeModule을 사용해야 합니다. GAC에 애플리케이션을 설치할 수 없는 경우 버전별 하위 디렉터리의 CommonFilesFolder에 설치해야 합니다.

공유되지 않는 어셈블리는 호출 애플리케이션과 함께 디렉터리에서 나란히 배치되어야 합니다.

추가 정보