Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Current »

[사족 : 사실 제일 먼저 다루려고 했으나, 워낙 익숙한 툴이라 잊혀졌습니다. API에 관심이 있으신 분은 어떤 개발툴도(사실 더 아는 개발툴도 없습니다😆 ) 좋으니 말씀해주시면 최대한 작성해보겠습니다]

VBA는 Excel를 다루는 사용자에게 가장 유용하고 접근이 쉬운 개발툴로, 이를 이용하여 MIDAS Civil API를 사용하는 예제를 작성해봅니다. 이미 다수의 문서에서 API 명령어에 대한 내용을 수록하였으므로, 여기서는 VBA를 다루는 것에 집중하도록 하겠습니다.

1️⃣ Json 데이터 다루기

VBA에서 Json 데이터를 손쉽게 다루기 위해서는 Dictionary Class 를 이용하는게 경험상 좋습니다.

초창기 예제 작성시 String 데이터로 작성하였으나, 이는 Json Data가 커질수록 사용하기 어렵고 (Hell of Chr(34)), 특히 Get Method를 이용하여 불러온 데이터를 다루기가 어렵습니다. 하지만 다행스럽게 Dictionary Class를 Json 포맷으로 Converter 해주는 Code가 있으므로, 이를 사용하면 더욱 더 편하게 작성이 가능합니다.

  • Dictionary Class

Dictionary Class는, 메뉴->도구->참조-> Microsoft Scripting Runtime을 활성화하여 사용합니다.

아래와 같이 활성화하지 않고 사용할 수 있지만, 불편하므로 활성화하고 사용하시길 추천드립니다.

'활성화하지 않고 사용할 시 선언 방법(late binding)
    Dim DicEx As Object 'Declare
    Set DicEx = CreateObject("Scripting.Dictionary") 'Create
  
'활성화 후 선언하는 방법(early binding)
    Dim DicEx As Dictionary 'Declare
    Set DicEx = New Dictionary 'Create

Dictionary Class는 다음과 같은 설정값을 가집니다.

속성

  • Count : 현재 저장된 개체의 갯수를 반환합니다.

  • Item(“KeyValue”) : 지정한 키의 Value를 불러오거나, 대체할 수 있습니다.

  • Key(“KeyValue”) : 지정한 키의 Key를 불러오거나, 대체할 수 있습니다.

  • CompareMode : Dictionary 개체에서 문자열 키를 비교하는 모드를 설정하여 반환합니다.(Binary, text 모드가 있으나, 굳이 변경할 필요는 없습니다.)

메소드

  • Add : 키와 값을 추가합니다. 존재하는 키이면 오류가 발생합니다.

  • Remove : 지정한 키와 할당되어 있는 값을 제거합니다. 존재하지 않는 키이면 오류가 발생합니다.

  • RemoveAll : 모든 키와 값을 제거합니다.

  • Exists : 지정한 키가 존재하면 True를 반환하고, 아니면 False를 반환합니다.

  • keys : 모든 키들을 배열로 반환합니다.

  • Items : 모든 값들을 배열로 반환합니다.

  • JsonConvertor

Json 양식을 VBA에서는 공식 지원하는 것이 없으므로, 아래 링크에 있는 JsonConvertor를 이용합니다.

GitHub - VBA-tools/VBA-JSON: JSON conversion and parsing for VBA

2️⃣ Dictionary 사용 예제

  • 예제1 : Dictionary Class의 기본적인 사용방법입니다.

 Example 1
Option Explicit
Option Base 1

Private Sub DicExample()

    Dim i As Integer
    Dim varItm, varMItm, varMKey As Variant
    Dim lngCnt As Long
    Dim blnKey As Boolean
    Dim dicExm As Dictionary
    
    'Key와 Value를 할당합니다.
    Set dicExm = New Dictionary
    
    dicExm.Add 1, "Apple"
    dicExm.Add 2, "Banana"
    dicExm.Add "3", "Cherry"
    
    'Dicrionay Class를 Json 포맷으로 변환하고 출력합니다.
    Debug.Print JsonConverter.ConvertToJson(dicExm)
    Debug.Print "=================================================="
    
    '객체의 갯수를 반환합니다.
    lngCnt = dicExm.Count
    Debug.Print lngCnt
    Debug.Print "=================================================="
    
    '특정 key를 가지는 value를 반환합니다.
    varItm = dicExm.Item(1)
    Debug.Print varItm
    Debug.Print "=================================================="
    
    '키값을 모두 변경합니다.
    dicExm.Key(1) = 3
    dicExm.Key("3") = 4
    Debug.Print JsonConverter.ConvertToJson(dicExm)
    Debug.Print "=================================================="
    
    '지정한 Key의 존재 유무를 파악합니다.
    blnKey = dicExm.Exists(4)
    Debug.Print blnKey
    Debug.Print "=================================================="
    
    'Key를 배열로 반환합니다.
    varMKey = dicExm.Keys
    For i = LBound(varMKey) To UBound(varMKey)
        Debug.Print varMKey(i)
    Next i
    Debug.Print "=================================================="
    
    'Value를 배열로 반환합니다.
    varMItm = dicExm.Items
    For i = LBound(varMItm) To UBound(varMItm)
        Debug.Print varMItm(i)
    Next i
    Debug.Print "=================================================="
    
    '모든 Key와 Value를 삭제합니다.
    dicExm.RemoveAll
    Debug.Print dicExm.Count
    Debug.Print "=================================================="
    
End Sub

  • 예제2 : 여러단계의 깊이를 가지는 Dictionary Class를 만드는 예제입니다.

VBA는 DeepCopy를 실행하는 명령어가 따로 없습니다(제가 알기론). 따라서 아래 예제와 같이 DeepCopy 형태의 Function을 이용할 수 있습니다. 혹은 아래와 같이 만들고 버리는 반복 작업을 통해 변수의 개수를 최소화하여 작성할 수 있습니다.

'예를 들어
Set dic = new dictionary 'Create

'dic 상위 클래스에 등록

Set dic = nothing 'Release

반복

이에 대한 내용은 깊은 복사 VS 얕은 복사 (velog.io) 이걸 참조하시면 이해가 빠르실 겁니다.

 Example 2
Private Sub DicExample2()
    
    Dim i, j As Integer
    Dim dicSub2, dicSub1, dicMain As Dictionary
    
    '배열을 Value에 등록합니다.
    Dim intItm(8) As Integer
    Set dicMain = New Dictionary
    
    For i = 1 To 8
        intItm(i) = i
    Next i
    
    dicMain.Add "Integer", intItm
    Debug.Print JsonConverter.ConvertToJson(dicMain)
    
    Set dicMain = Nothing
    
    '여러단계의 깊이를 가지는 Dicionary 구조 예제입니다
    'GRUP를 예제로 다음 구조를 가지는 Dictionary를 만들어 봅니다
    '"GRUP":{
    '    "1":{
    '       "NAME":"G1"
    '       "E_LIST":[1,2,3,4,5]
    '       },
    '    "2":{
    '       "NAME":"G2"
    '       "E_LIST":[6,7,8,9,10]
    '       }
    '      }
    Dim intElmList(5) As Integer
    Set dicSub1 = New Dictionary: Set dicMain = New Dictionary
    
    For i = 1 To 2
        
        For j = 1 To 5
            intElmList(j) = 5 * (i - 1) + j
        Next j
        
        Set dicSub2 = New Dictionary    'Setting dicSub2 as new dictonary
        dicSub2.Add "NAME", "G" & i
        dicSub2.Add "E_LIST", intElmList
        
        'dicSub2.Add "E_LIST", Array(1, 2, 3, 4, 5) '간단한 데이터는 Array 함수로 각각 처리할 수도 있습니다.
        
        dicSub1.Add i, dicSub2
        Set dicSub2 = Nothing           'Release dicsub2
        
    Next i
            
    dicMain.Add "GRUP", dicSub1
    Debug.Print JsonConverter.ConvertToJson(dicMain)
    Set dicSub1 = Nothing: Set dicMain = Nothing
    
    'LCOM 과 같이 Dictionary 구조를 Array로 가지는 것에 대한 예제입니다.
    '다음 구조를 예제로 한번 작성해 보겠습니다.
    '    {
    '    "LCOM": {
    '        "1": {
    '            "NAME": "DC",
    '            "KIND": "GEN",
    '            "ACTIVE": "ACTIVE",
    '            "bES": false,
    '            "iTYPE": 0,
    '            "DESC": "",
    '            "iSERV_TYPE": 0,
    '            "nLCOMTYPE": 0,
    '            "nSEISTYPE": 0,
    '            "vCOMB": [
    '                {
    '                    "ANAL": "CS",
    '                    "LCNAME": "Dead Load",
    '                    "FACTOR": 1
    '                }
    '            ]
    '        },
    '        "2": {
    '            "NAME": "CR/SH",
    '            "KIND": "GEN",
    '            "ACTIVE": "ACTIVE",
    '            "bES": false,
    '            "iTYPE": 0,
    '            "DESC": "",
    '            "iSERV_TYPE": 0,
    '            "nLCOMTYPE": 0,
    '            "nSEISTYPE": 0,
    '            "vCOMB": [
    '                {
    '                    "ANAL": "CS",
    '                    "LCNAME": "Creep Secondary",
    '                    "FACTOR": 1
    '                },
    '                {
    '                    "ANAL": "CS",
    '                    "LCNAME": "Shrinkage Secondary",
    '                    "FACTOR": 1
    '                }
    '            ]
    '        }
    '    }
    
    Dim dicComb(), dicCmn As Dictionary
    
    Set dicCmn = New Dictionary
    Set dicSub1 = New Dictionary: Set dicMain = New Dictionary
   
    dicCmn.Add "NAME", ""
    dicCmn.Add "KIND", "GEN"
    dicCmn.Add "ACTIVE", "ACTIVE"
    dicCmn.Add "bES", False
    dicCmn.Add "iTYPE", 0
    dicCmn.Add "DESC", ""
    dicCmn.Add "iSERV_TYPE", 0
    dicCmn.Add "nLCOMTYPE", 0
    dicCmn.Add "nSEISTYPE", 0
    dicCmn.Add "vCOMB", ""
    
    dicSub1.Add 1, DeepCopy(dicCmn) 'for DeepCopy
    dicSub1.Add 2, DeepCopy(dicCmn) 'for DeepCopy
    
    ReDim dicComb(1)
    Set dicComb(1) = New Dictionary
    
    dicComb(1).Add "ANAL", "CS"
    dicComb(1).Add "LCNAME", "Dead Load"
    dicComb(1).Add "FACTOR", 1
    
    dicSub1.Item(1)("NAME") = "DC"
    dicSub1.Item(1)("vCOMB") = dicComb
    
    ReDim dicComb(2)
    For i = 1 To 2: Set dicComb(i) = New Dictionary: Next i
    
    dicComb(1).Add "ANAL", "CS"
    dicComb(1).Add "LCNAME", "Creep Secondary"
    dicComb(1).Add "FACTOR", 1
    
    dicComb(2).Add "ANAL", "CS"
    dicComb(2).Add "LCNAME", "Shrinkage Secondary"
    dicComb(2).Add "FACTOR", 1
    
    dicSub1.Item(2)("NAME") = "CR/SH"
    dicSub1.Item(2)("vCOMB") = dicComb
    
    dicMain.Add "LCOM", dicSub1
    
    Debug.Print JsonConverter.ConvertToJson(dicMain)
    
End Sub

Private Function DeepCopy(dic As Dictionary) As Dictionary
    
    Dim Key As Variant
    
    Set DeepCopy = Nothing
    
    Set DeepCopy = New Dictionary
    For Each Key In dic.Keys
        DeepCopy.Add Key, dic(Key)
    Next Key
    
End Function

3️⃣ JsonConvertor 예제

  • JsonConvertor를 다루는 간단한 예제입니다.

 JsonConverter
Private Sub JsonConverterEX()

    Dim Json As String
    Dim dicJson As Dictionary
    
    'Json 포맷의 String Data 작성
    Json = "{" & Chr(34) & "NODE" & Chr(34) & ":{" & Chr(34) & "1001" & Chr(34) & ":{" _
            & Chr(34) & "X" & Chr(34) & ":1.1," & Chr(34) & "Y" & Chr(34) & ":2.2," & Chr(34) & "Z" & Chr(34) & ":3.3}}}"
    
    Debug.Print Json
    
    'Json 포맷의 String Data를 Dictionary로 변환
    Set dicJson = JsonConverter.ParseJson(Json)
    Debug.Print dicJson.Item("NODE")("1001")("X")
    Debug.Print dicJson.Item("NODE")("1001")("Y")
    Debug.Print dicJson.Item("NODE")("1001")("Z")
    
    dicJson.Item("NODE")("1001")("X") = 4.4
    dicJson.Item("NODE")("1001")("Y") = 5.5
    dicJson.Item("NODE")("1001")("Z") = 6.6
    
    'Dictionay Data를 Json String 데이터로 변환
    Dim dicTojson As String
    dicTojson = JsonConverter.ConvertToJson(dicJson)
    
    Debug.Print dicTojson
    
End Sub

4️⃣ Simple Beam 예제

VBA에서 Http Request는 “WinHttp.WinHttpRequest.5.1”를 사용합니다.

최대한 많은 API를 이용하여 단순보를 작성하는 예제를 만들었습니다. 첨부파일을 참고해주세요.

VBA Ex.mp4


5️⃣ 추가

(1) Runtime error - Timeout

아래 코드는 위에 예제에서 활용한 HttpRequest Function 입니다.

Function WebRequest(Method As String, Command As String, Body As String) As String

    Dim TCRequestItem As Object
    Dim URL As String
    
    Set TCRequestItem = CreateObject("WinHttp.WinHttpRequest.5.1")
    URL = "http://localhost:10024"
    URL = URL & Command
    TCRequestItem.Open Method, URL, False
    TCRequestItem.SetRequestHeader "Content-type", "application/json"
    TCRequestItem.Send Body
    WebRequest = TCRequestItem.ResponseText
    
End Function

간혹, 제품으로부터 Response를 받기까지 시간이 지연되어, 아래와 같은 경고창과 함께 VBA 작동이 멈추는 경우가 있습니다. (절점과 요소수가 많을 경우 등등)

그럴 경우 아래와 같이 코드 한 줄을 삽입해주시면 됩니다. 단위는 Milliseconds 입니다.

Function WebRequest(Method As String, Command As String, Body As String) As String

    Dim TCRequestItem As Object
    Dim URL As String
    
    Set TCRequestItem = CreateObject("WinHttp.WinHttpRequest.5.1")
    
    'SetTimeouts(resolveTimeout, ConnectTimeout, SendTimeout, ReceiveTimeout)
    TCRequestItem.SetTimeouts 60000, 60000, 60000, 60000
    
    URL = "http://localhost:10024"
    URL = URL & Command
    TCRequestItem.Open Method, URL, False
    TCRequestItem.SetRequestHeader "Content-type", "application/json"
    TCRequestItem.Send Body
    WebRequest = TCRequestItem.ResponseText
    
End Function

(2) float point error (부동소수점 에러)

이 항은 수치해석에 잘 알려진 에러인 부동소수점 에러를 VBA에서 어떻게 다루는지에 대해 설명해봅니다.

부동소수점의 에러는 컴퓨터가 받아들이는 숫자를 처리하는 방식에 의해서 생기는 걸로 이해하고 있습니다.

(관심이 있으신 분은 Floating-point arithmetic - Wikipedia 여길 참고해보세요.)

예를 들어 다음과 같은 코드를 실행시켜 보겠습니다.

Option Explicit

Sub Test()

    Dim i As Integer
    Dim sngVar As Single
    Dim dblVar As Double
    
    For i = 1 To 10000
        sngVar = sngVar + 0.0001
    Next i
    
    Debug.Print TypeName(sngVar) & " : " & sngVar
    
    For i = 1 To 10000
        dblVar = dblVar + 0.0001
    Next i
    
    Debug.Print TypeName(dblVar) & " : " & dblVar
    
End Sub

0.0001를 만번 더했는데, 원했던 결과인 1이 나오질 않습니다. 게다가 자료형에 따라 그 오차도 차이가 있습니다. 이런 문제는 프로그램이면 모두 가지고 있는 문제입니다. 아래는 엑셀 예제입니다. 간단한 연산임에도 불구하고 9번째 행은 우리가 원하는 결과를 보여주지 못합니다.

이런 에러는 간단하게 유효 자릿수를 주는 방식으로 해결할 수도 있지만, 조금도 근본적인 해결방법이 있는 경우가 있습니다. 바로 자료형을 바꿔주는 겁니다.

VBA에서 이런 문제에 쓸 수 있는 자료형은 바로 Decimal 자료형입니다.

아래와 같이 0.0001를 Decimal 자료형으로 바꿔주어 더해주거나, Decimal 자료형으로 선언해서 쓰는 방법 등이 있습니다.(Decimal 은 Variant로 선언합니다.)

Option Explicit

Sub Test()

    Dim i As Integer
    Dim sngVar As Single
    Dim dblVar As Double
    Dim decVar1 As Variant
    Dim decVar2 As Variant
    
    For i = 1 To 10000
        sngVar = sngVar + CDec(0.0001)
    Next i
    
    Debug.Print TypeName(sngVar) & " : " & sngVar
    
    For i = 1 To 10000
        dblVar = dblVar + CDec(0.0001)
    Next i
    
    Debug.Print TypeName(dblVar) & " : " & dblVar
       
    For i = 1 To 10000
        decVar1 = decVar1 + CDec(0.0001)
    Next i
    
    Debug.Print TypeName(decVar1) & " : " & dblVar

    For i = 1 To 10000
        decVar2 = decVar2 + 0.0001
    Next i
    
    Debug.Print TypeName(decVar2) & " : " & dblVar

End Sub

  • No labels