VBA Subassembly를 .NET으로 변환하기
이 장은 기존 커스텀 COM/VBA에서 작성된 subassembly를 .NET으로 변환하는 절차를 설명한다.
VBA 커스텀 subassembly가 AutoCAD Civil 3D 2015에서 아직 지원된다 할지라도, VBA 지원은 더이상 하지 않고 향후 릴리즈에서 중단될 것이다. 추가적으로, .NET subassembly는 몇몇 이점을 제공한다: 그들은 작성하고 유지보수하기가 더 쉽다, 그리고 가장 중요한것, 그들은 눈에 띄는 성능 증가를 제공한다. 평균적으로, 그들은 VBA에서 작성된 코드보다 50% 정도 더 빠르고, 성능 증가는 복잡한 도면에서 더 커질수 있다.
당신의 커스텀 subassembly를 .NET으로 변환한뒤 카탈로글 파일로 가져오기를 하고, 당신은 당신의 도면에 있는 assembly 를 업데이트할 필요가 있다. 그리고 corridor를 리빌드 하라. 예전 VBA subassembly를 새로운 .NET으로 교체하는 몇몇 샘플 코드는 이 부록의 마지막 절에 있다.
이 절의 주제
- 절차
절차
이 절은 VBA subassembly를 VB.NET으로 변환하가 위해 요구되는 단계를 설명한다. 비록 이 절차가 subassembly를 VB.NET으로 변환하는 것만 설명하지만, 당신은 subassembly를 다른 .NET언어(C#같은) 로 변환하기 위해 비슷하게 접근할 수 있다. 자세한 내용은 정규식에 대한 MSDN 도움말과 파일에서의 바꾸기를 보라.
이 절의 주제
- Visual Basic.NET Subassembly 모듈 만들기
- Subassembly 코드 복사하기
- VBA 코드를 Visual Basic .NET 코드로 변환하기
- 최종 조정
- 새 subassembly를 설치하기
- VBA subassembly를 교체하기
Visual Basic.NET subassembly 모듈 만들기
당신의 subassembly를 위한 모듈 만드는 것부터 시작하라. 당신은 새 모듈을 C3DStockSubassembly 프로젝트에 추가할 수 다, 아니면 직접 .NET subassembly 프로제젝트를 생성하고 거기에 새 클래스 모듈을 추가할 수 있다. 새 프로젝트는 Visual Studio “Class Library” 탬플릿을 사용해야 한다, 그리고 다음 파일들을 참조해야 한다.
- acdbmgd.dll
- acmgd.dll
- AecBaseMgd.dll
- AeccDbMgd.dll
CodeSpecific.vb, SATemplate.vb 그리고 Utilities.vb도 당신의 프로젝트에도 포함하라.
다음 프레임웍을 Visual Basic subassembly 클래스 모듈에 추가하라:
Public Class UserDefinedSA
Inherits SATemplate
' Member Variables.
Protected Overrides Sub GetLogicalNamesImplement(ByVal corridorState As Autodesk.Civil.Runtime.CorridorState)
' Todo
End Sub
Protected Overrides Sub GetInputParametersImplement(ByVal corridorState As CorridorState)
' Todo
End Sub
Protected Overrides Sub GetOutputParametersImplement(ByVal corridorState As CorridorState)
' Todo
End Sub
Protected Overrides Sub DrawImplement(ByVal corridorState As CorridorState)
' Todo
End Sub
End Class
Inherits SATemplate
' Member Variables.
Protected Overrides Sub GetLogicalNamesImplement(ByVal corridorState As Autodesk.Civil.Runtime.CorridorState)
' Todo
End Sub
Protected Overrides Sub GetInputParametersImplement(ByVal corridorState As CorridorState)
' Todo
End Sub
Protected Overrides Sub GetOutputParametersImplement(ByVal corridorState As CorridorState)
' Todo
End Sub
Protected Overrides Sub DrawImplement(ByVal corridorState As CorridorState)
' Todo
End Sub
End Class
Note that:
- 클래스는 SATemplate에서 상속되어야 한다.
- DrawImplement() 매서드를 오버라이드 해야 한다, 그렇지 않으면 subassembly는 아무것도 하지 않을것이다.
- 다른 오버라이드된 매서드는 사용되지 않는다면 제거될수도 있다.
- 당신 소유의 함수와 서브루틴을 클래스 범위내에서 추가할 수 있다.
Subassembly 코드 복사
VBA 모듈(.bas)에서 원본 코드를 새 클래스의 해당위치에 복사하라:
From
|
To
|
UserdefinedSA_GetLogicalNames()
|
GetLogicalnamesImplement()
|
UserdefinedSA _GetInputParameters()
|
GetInputParametersImplement()
|
UserdefinedSA _GetOutputParameters()
|
GetOutputParametersImplement()
|
UserdefinedSA()
|
DrawImplement()
|
Const variables
|
Member variables section
|
VBA 코드를 Visual Basic .NET 코드로 변환
이제 변환작업을 시작하라. 다음 섹션에서는 주요 단계를 대략 설명한다. stock subassembly DaylighBench.vb에서의 코드는 삽화로 사용된다.
Step 1: 필요한 namespace 가져오기
Autodesk.Civil.Roadway와 Autodesk.Civil namespace를 추가하는 것이 필요하다. 이 두 namespace에 있는 맴버들이 subassembly에서 자주 사용되기 때문이다. 당신의 subassembly는 추가적인 namespace를 요구할 수도 있다. 이들은 stock Civil3D subassembly에 가져오기된 namespace들이다.(C3DStockSubassemblies.vbproj)
- Autodesk.Civil
- Autodesk.Civil.ApplicationServices
- Autodesk.Civil.DatabaseServices
- Autodesk.Civil.Land
- Autodesk.Civil.Land.DatabaseServices
- Autodesk.Civil.Land.Settings
- Autodesk.Civil.Roadway
- Autodesk.Civil.Roadway.DatabaseServices
- Autodesk.Civil.Roadway.Settings
- Autodesk.Civil.Runtime
- Autodesk.Civil.Settings
Step 2: On Error… 구문을 모두 제거
GetLogicalNamesImplement(), GetInputParameterImplement(), 그리고 GetOutputParametersImplement() 매서드에 있는 On Error 구문을 모두 제거하라. DrawImplement()에서 이 구문들을 주석처리하라, 당신은 Step 14에서 그 코드를 다시 사용할 것이기 때문이다.
정규 표현식:
- Find: On Error{.*}
- 이 구문들을 삭제하기를 원한다면 교체 필드를 빈칸으로 둔다. 만약 주석처리를 하기를 원한다면 ‘On Error\1을 사용하라.
Step 3: Exit Sub와 Error Handler를 제거
모든 각 subroutine의 끝에 있는 Exit Sub와 Error Handler 구문을 모두 제거하라. VBa에서 당신은 각 서브루틴의 끝에서 다음 코드를 볼 수도 있다 - 제거하거나 Step 14에서 error 처리 로직을 변환할때 Catch 구문으로 이동하라.
Exit Sub
ErrorHandler:
RecordError Err.Number, Err.Description, Err.Source
ErrorHandler:
RecordError Err.Number, Err.Description, Err.Source
정규 표현식:
- Find: Exit Sub{ *}\n{ *}\n{ *}ErrorHandler\:{ *}\n{ *}RecordError.+{ *}\n{ *}{End Sub}
- Replace: \8
Step 4: oRwyState definition 제거.
각 서브루틴의 시작부분에 있는 다음 코드를 제거하라.:
' Get the roadway state object
Dim oRwyState As AeccRoadwayState
Set oRwyState = GetRoadwayState()
Dim oRwyState As AeccRoadwayState
Set oRwyState = GetRoadwayState()
corridorState object는 이미 서브루틴의 인수에 의해서 전달되었다, 그래서 자체적으로 가져올 필요가 없다.
정규 표현식:
- Find: {'.*}{ *}\n{ +}Dim.+As AeccRoadwayState{ *}\n{ *}.+= GetRoadwayState\(\)
- Replace: leave blank to delete these statements
Step 5: corridorState와 oRwyState를 교체
VBA에서 사용된 oRwyState변수의 모든 인스턴스는 .NET에서 corridorState으로 이름이 변경되어야 한다. corridorState 변수는 인수에 의해서 전달된다.
정규 표현식:
- Find: oRwyState
- Replace: corridorState
Step 6: 파리미터 값에 접근할때 에러 체크하기
VBA에서, 파라미터 Value() 매서드는 key가 존재하지 않을경우, null 값을 리턴한다. .NET API에서, 같은 코드는 exception을 던진다. 당신이 파라미터에 접근할때, 이 경우를 catch하기위해 Try / Catch 블럭을 사용할 필요가 있다.
' VBA:
vCutSlope = oParamsDouble.Value("CutSlope")
If IsEmpty(vCutSlope) Then vCutSlope = c_dCutSlopeDefault
vCutSlope = oParamsDouble.Value("CutSlope")
If IsEmpty(vCutSlope) Then vCutSlope = c_dCutSlopeDefault
이 코드는 다음과 같이 변경 될수 있다.:
' .NET:
Try
vCutSlope = oParamsDouble.Value("CutSlope ")
Catch
vCutSlope = c_ dCutSlopeDefault
End Try
Try
vCutSlope = oParamsDouble.Value("CutSlope ")
Catch
vCutSlope = c_ dCutSlopeDefault
End Try
정규 표현식:
- Find: ^{.+ =.+\.Value\(.+\)}\n{ +}If IsEmpty.+Then{.+}
- Replace: Try\n\1\nCatch\n\3\nEnd Try\n
Step 7: RecordError() 업데이트
전역함수인 RecordError()는 Utilities.RecordError()로 교체된다.
The global function RecordError() is replaced by Utilities.RecordError().
' VBA:
RecordError(aeccRoadwayErrorValueTooLarge, "RoundingTesselation", "DaylightBench")
RecordError(aeccRoadwayErrorValueTooLarge, "RoundingTesselation", "DaylightBench")
Change to:
'.NET:
Utilities.RecordError(corridorState, CorridorError.ValueTooLarge, "RoundingTesselation", "DaylightBench")
Utilities.RecordError(corridorState, CorridorError.ValueTooLarge, "RoundingTesselation", "DaylightBench")
정규 표현식:
- Find: {Record}{Warning|Error}{\(}{aecc}{RoadwayError}
- Replace: Utilities.Record\2\3roadwayState,RoadwayError.
Step 8: Utilities 변수와 전역 변수를 교체
VBA subassembly code에서, 당신은 g_iRight, g_sSide와 같은 전역 변수를 볼 것이다. 그들은 “g_” 접두어를 가진다. 이 전역분수의 대부분은 Utilities 클래스로 이동되고 이름이 변경되었다.
다음 표는 몇몇 일반적으로 사용된 전역변수와, Utilities 클래스에서 대응하는 것을 나열한다. 더 많은 정보는 Utilities 클래스의 정의를 참조하라.
From
|
To
|
g_sSide
|
Utilities.Side
|
g_iLeft
|
Utilities.Left
|
g_iRight
|
Utilities.Right
|
g_iTrue
|
Utilities.ITrue
|
Rounding_Option.NoneType
|
Utilities.RoundingOption.NoneType
|
CutSituation
|
Utilities.FillOrCut.FillSituation
|
SubbaseType
|
Utilities.ShoulderSubbaseType.Subbase
|
Step 9: 열거식 이름 변경
VBA subassembly 코드에서, 거의 모든 COM 열거식은 aeccParamLogicalNameTypeAlignment처럼 “aecc” 접두어를 가진다.그들을 .NET 열거식에 대응하도록 교체하라.
규칙에 따르면, 대응 .NET 열거식은 “aecc” 접두어를 제거하고 상세 카테고리를 자식맴버로 만들어서 이름을 정한다. 예를들어, aeccParamLogicalNameTypeAlignment는 ParamLocicalNameType.Alignment가 된다.
다음 표는 몇몇 일반적으로 사용되는 COM 열거식과 .NET 열거식과 대응되는것을 나열한다.
From
|
To
|
aeccParamLogicalNameTypeAlignment
|
ParamLogicalNameType.Alignment
|
aeccRoadwayModeLayout
|
CorridorMode.Layout
|
aeccParamAccessOutput
|
ParamAccessType.Output
|
Step 10: 이름이 변경되는 종류
COM 형식과 그들의 대응 .NET 형식을 교체하라.
다음 표는 몇몇 일반적으로 사용되는 COM 형식과 그들의 대응 .NET 형식을 나열한다.
From
|
To
|
IAeccParamsDouble
|
ParamDoubleCollection
|
IAeccRoadwayLinks
|
LinkCollection
|
IAeccParam
|
Param
|
AeccRoadwayLink
|
Link
|
AeccParamLong
|
ParamLong
|
정규 표현식:
- Convert IAeccParamsAlignment and AeccParamsAlignment to ParamAlignmentCollection
- Find: { }I*AeccParams{.+}{ |\n}
- Replace: \1Param\2Collection\3
- Convert: IAeccRoadwayLinks to LinkCollection
- Find: { }I*AeccRoadway{.*}s{ |\n}
- Replace: \1\2Collection\3
- Convert AeccRoadwayLink to Link
- Find: { }(I|())AeccRoadway{Link|Shape}{ |\n}
- Replace: \1\2\3
- Convert AeccParamLong to ParamLong
- Find: { }I*AeccParam{Long|Bool|Double|Point|String|()}{ |\n}
- Replace: \1Param\2\3
이 네가지 정규 표현식은 모든 변경을 커버하지는 않는다, 그래서 당신은 수동으로 추가적인 변경을 해야 할 것이다.
Step 11: Object를 기본제공 형식으로 변경
Subassembly가 사용하는 Object를 사용할때, 그것은 기본제공 형식으로 변경되어야 한다. Object는 안전한 형식이 아니기 때문이다.
' .NET Before:
Dim vSide As Object
Try
vSide = oParamsLong.Value(Utilities.Side)
Catch
vSide = SideDefault
End Try
Dim vSide As Object
Try
vSide = oParamsLong.Value(Utilities.Side)
Catch
vSide = SideDefault
End Try
' .NET After:
Dim vSide As Long
Try
vSide = oParamsLong.Value(Utilities.Side)
Catch
vSide = SideDefault
End Try
Dim vSide As Long
Try
vSide = oParamsLong.Value(Utilities.Side)
Catch
vSide = SideDefault
End Try
Step 12: 코드 변수를 .NET을 사용하도록 업데이트
코드 이름을 위한 새 규약에 일치시키기 위해 g_All, g_s그리고 각 변수이름에 있는 접두어를 제거하라.
From
|
To
|
g_AllCodes.g_sHinge_Cut.sCode
|
Codes.HingeCut.Code
|
g_AllCodes.g_sDaylight.sCode
|
Codes.Daylight.Code
|
g_AllCodes.g_sDaylight_Cut.sCode
|
Codes.DaylightCut.Code
|
정규 표현식:
- Find: g_All{Codes\.}g_s{.+\.}s{.+}
- Replace: \1\2\3
Step 13: 배열 정의를 업데이트
VBA에서, 많은 배열이 인덱스 “1”에서 시작하도록 정의된다. 이것은 .NET에서는 허용되지 않는다, 그래서 당신은 그들의 정의를 변경해야한다.
대부분 상황에서, 당신은 배열 정의가 모든 다른 변경을 할 필요가 없도록 수정할 수 있다. 배열 요소 0은 정의되지만, 사용되지 않은 채로 남아 있다.
' .NET Before:
Dim sPointCodeArray(1 To 9, 0 To 1) As String
Dim sPointCodeArray(1 To 9, 0 To 1) As String
' .NET After:
Dim sPointCodeArray(0 To 9, 0 To 1) As String
Dim sPointCodeArray(0 To 9, 0 To 1) As String
만약 이 배열이 매서드에 인수로 전달된다면, 당신은 더 많은 작업을 할 필요가 있을것이다. 이 경우, 배열은 인덱스 “0”부터 사용된다. 당신은 이 배열을 사용하는 모든 코드의 인덱스 번호매김을 변경해야 한다.
Step 14: 에러 핸들링을 수정
VBA에서, On Error Resume Next 와 On Error GoTo ErrorHandler 구문은 에러를 처리하기 위해서 사용된다. 그러나, .NET에서는 에외처리가 대신 사용된다. Try...Catch 블럭을 사용해서 에러가 감지된 모든 경우를 수정하라.
' VBA:
On Error Resume Next
Dim oTargetDTM As IAeccSurface
Set oTargetDTM = oParamsSurface.Value("TargetDTM")
If oTargetDTM Is Nothing Then
' Error handling code goes here
Exit Sub
End If
On Error GoTo ErrorHandler
On Error Resume Next
Dim oTargetDTM As IAeccSurface
Set oTargetDTM = oParamsSurface.Value("TargetDTM")
If oTargetDTM Is Nothing Then
' Error handling code goes here
Exit Sub
End If
On Error GoTo ErrorHandler
Change error handling to:
' .NET:
Dim oTargetDTMId As ObjectId
Try
oTargetDTMId = oParamsSurface.Value("TargetDTM")
Catch
' Error handling code goes here
Exit Sub
End Try
Dim oTargetDTMId As ObjectId
Try
oTargetDTMId = oParamsSurface.Value("TargetDTM")
Catch
' Error handling code goes here
Exit Sub
End Try
Step 15: 데이타베이스 object를 위한 새 규칙
COM API에서, 인수와 반환값은 모두 데이타베이스 object 인스턴스를 참조한다. 그러나, .NET API에서, 인수와 반환값은 데이타베이스의 object의 ObjectId이다, object 자체가 아니다.
' VBA:
Dim oTargetDTM As IAeccSurface
Set oTargetDTM = oParamsSurface.Value("TargetDTM")
Dim oTargetDTM As IAeccSurface
Set oTargetDTM = oParamsSurface.Value("TargetDTM")
.NET에서는 이렇게 될것이다.:
' .NET:
Dim oTargetDTMId As ObjectId
Try
oTargetDTMId = oParamsSurface.Value("TargetDTM")
Catch
End Try
Dim oTargetDTMId As ObjectId
Try
oTargetDTMId = oParamsSurface.Value("TargetDTM")
Catch
End Try
그 다음, 데이타 베이스 object의 인스턴스를 사용하려면, 당신은 TransactionManager.GetObject()를 사용해서 데이타베이스 object를 open해야 한다.
' VBA:
Dim oCurrentAlignment As AeccAlignment
GetAlignmentAndOrigin(oRwyState, oCurrentAlignment, oOrigin)
Dim oCurrentAlignment As AeccAlignment
GetAlignmentAndOrigin(oRwyState, oCurrentAlignment, oOrigin)
In .NET:
' .NET:
Dim currentAlignmentId As ObjectId
Dim currentAlignment As Alignment
Utilities.GetAlignmentAndOrigin(corridorState, currentAlignmentId, origin)
' ...
Dim db As Database = Autodesk.AutoCAD.DatabaseServices.HostApplicationServices.WorkingDatabase
Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
currentAlignment = tm.GetObject(currentAlignmentId, OpenMode.ForRead, false, false)
Dim currentAlignmentId As ObjectId
Dim currentAlignment As Alignment
Utilities.GetAlignmentAndOrigin(corridorState, currentAlignmentId, origin)
' ...
Dim db As Database = Autodesk.AutoCAD.DatabaseServices.HostApplicationServices.WorkingDatabase
Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
currentAlignment = tm.GetObject(currentAlignmentId, OpenMode.ForRead, false, false)
최종 조정
위 규칙들은 VBA 코드를 Visual Basic .NET 코드로 작업하기 위해 변환하는데 필요한 변경사항의 주요항목에 대한 설명이다. 당신의 subassembly의 상황에 따라 , 몇몇 문법적인 오류가 있을 수 있다.
새 Subassembly 설치
Autodesk Tool Catalog 파일(.atc 파일, [DataLocation]\Autodesk\C3D2011\enu\Tool Catalogs\Road Catalog)을 수정하여 당신의 새 subassembly를 Civil 3D Subassembly 카탈로그에 나열해야 한다.
Note:
위 경로에서 DataLocation은 OS에 따라 달라진다.
- Windows XP에서는 C:\Documents and Settings\All Users\Application Data\ 이다.
- Windows Vista와 Windows 7에서는C:\ProgramData\Autodesk\ 이다.
.NET subassembly를 위한 .atc 파일 형식은 2가지 새 태그를 사용하는것 빼고는 VBA subassembly와 거의 동일하다 :
<GeometryGenerateMode>UseDotNet</GeometryGenerateMode>
<GeometryGenerateMode>은 당신이 .NET을 사용하고 있다는 것을 Civil 3D에 알린다. 그것은 <Tool> 태그내에 있다.
<DotNetClass Assembly="%AECCCONTENT_DIR%\C3DStockSubassemblies.dll">Subassembly.NewCurb</DotNetClass>
<DotNetClass> 은 .NET subassembly를 사용할때 <Macro> 태그 대신 사용된다.
VBA Subassembly 교체
VBA 사용자 subassembly가 .NET으로 변경되고, 카탈로그에 설치되고 나면, 도면이 업데이트 되어서 새로운 코드를 사용해야 한다. 아래는 예제이다. 이 작업을 수행하는 .NET 매크로이다. 이 매크로는 name(“VBASubassembly”)에 의해서 subassembly를 가져온다. 그다음 새로운 SubassemblyGenerator object를 만들고, mode(“UseDotNet”)에 전달되고, 새 subassembly에 dll이 만들어지고, .NET subassembly의 이름이 만들어 진다. 마지막으로 subassembly GeometryGenerator 파라미터가 새 SubassemblyGenerator에 설정된다. 트랜잭션이 커밋될때 subassembly가 교체된다.
[CommandMethod("ConvertVbaSA")]
public void ConvertVbaSA()
{
using(Transaction trans = m_transactionManger.StartTransaction())
{
ObjectId saId = Autodesk.Civil.ApplicationServices.CivilApplication.ActiveDocument.SubassemblyCollection["VBASubassembly"];
Subassembly sa = trans.GetObject(saId, OpenMode.ForWrite) as Subassembly;
SubassemblyGenerator genData = new SubassemblyGenerator(SubassemblyGeometryGenerateMode.UseDotNet, "C3DStockSubassemblies.dll", "Subassembly.DotNetSubassembly");
sa.GeometryGenerator = genData;
trans.Commit();
}
}
public void ConvertVbaSA()
{
using(Transaction trans = m_transactionManger.StartTransaction())
{
ObjectId saId = Autodesk.Civil.ApplicationServices.CivilApplication.ActiveDocument.SubassemblyCollection["VBASubassembly"];
Subassembly sa = trans.GetObject(saId, OpenMode.ForWrite) as Subassembly;
SubassemblyGenerator genData = new SubassemblyGenerator(SubassemblyGeometryGenerateMode.UseDotNet, "C3DStockSubassemblies.dll", "Subassembly.DotNetSubassembly");
sa.GeometryGenerator = genData;
trans.Commit();
}
}
댓글 없음:
댓글 쓰기