NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# C# .NET Core 매크로 프로그램 만들기. (변수 2부)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 오늘은 변수에 저장되어 있는 데이터를 어떻게 처리해야 하는지에 대해서 알아볼건데요. 1부에서 변수를 만들고, 어떻게 사용자에게 표시할지에 대해 알아봤습니다. 이 내용을 먼저 읽어보고 2부 내용을 보면 이해하기가 좀 더 쉬울겁니다. 논리적인 프로그래밍 코드보다는 시각적으로 보는게 이해하는게 빠르니까요.

     

    엔지엠 매크로에서 변수를 처리하는 방법은 단순합니다. 우선, 액션을 실행하기 위한 조건들이 있는데요. 일반적인 설정은 기본값으로 셋팅되어 있어서 딱히 변경할 필요가 없을수도 있습니다. 예를 들어서 마우스 클릭의 경우 대부분 좌클릭이기 때문에 기본값으로 설정된 Left 속성을 변경할일이 없죠? 하지만, 좌표는 0, 0으로 설정되어 있어서 이 부분은 실제로 클릭해야 할 위치로 바꿔줘야 합니다. 이렇듯 액션이 실행될 때 속성에 설정된 값을 토대로 동작한다는걸 알 수 있습니다.

     

    변수로부터 가져올 데이터가 마우스 클릭 좌표라고 생각 해보면, 액션이 실행되기 전 변수로부터 마우스 클릭 좌표를 가져와서 마우스 액션의 좌표에 넣어줘야 원하는데로 동작할겁니다. 액션이 실행된 후 어떤 결과 값을 변수에 추가하려면 액션이 완료된 후 해당 값을 변수에 추가해야 하기 때문에 전체적인 프로세스는 아래와 같습니다. 메소드 이름을 보면 대략 알 수 있을텐데요. GetVariableProcess에서 변수 데이터를 가져와서 action에 넣어주고, action.Excute로 실행합니다. 마지막으로 SetVariableProcess에서 변수에 처리된 속성 값을 추가합니다.

                    foreach (var action in actions)
                    {
                        GetVariableProcess(action);
    
                        Manager.Output.WriteLine($"{action.DisplayText}", isBold: true);
                        action.Excute(this);
                        Manager.Output.WriteLine(action.ToString());
    
                        SetVariableProcess(action);
                    }

     

    변수 가져오는 코드에서 변수 추가 액션은 제외시켜야 합니다. 변수를 처리하는 액션까지 동작할 필요는 없으니까요.

            private void GetVariableProcess(ActionModel action)
            {
                if (action is Model.Action.Variables.AddVariableModel)
                    return;

     

    이전 시간에 만든 변수 목록을 반복하면서 데이터를 처리 해줍니다.

                if (action.GetVariables.Count > 0)
                {
                    System.Collections.ICollection collection = action.GetVariables;
    
                    foreach (Model.Data.VariableItem item in collection)

     

    변수 레벨에 따라서 메모리에 만들어진 목록을 가져오도록 처리했습니다.

                        if (item.IsGlobal)
                            variables = Manager.Variables;
                        else
                        {
                            if (HasParent && item.IsParent)
                                variables = Parent?.Variables;
                            else
                                variables = Variables;
                        }

     

    변수 레벨은 총 3단계입니다. 글로벌 변수는 클라이언트 전체에서 사용하는 변수로 여러개의 스크립트가 공유하는 저장소입니다. 두번째는 Parent Variables로 플레이어가 만든 자식 플레이어가 부모 변수에 접근할 수 있는 변수입니다. 전역 변수라기 보다는 상위 플레이어에 접근할 수 있는 변수입니다. 마지막으로 지역 변수입니다. 보통 프로그래밍에서 말하는 전역 변수와 지역 변수인데요. 부모 변수는 base 정도 되겠습니다.

     

    여기서 중요한 부분이 있습니다. 프로그램을 만들다보면 의도하지 않게 메모리 릭(누수: Leak)이 발생하게 됩니다. C나 C++을 제외한 자바나 닷넷 또는 파이썬과 같은 일반적으로 많이 사용하는 프로그래밍 언어들은 자체적으로 메모리를 관리해줍니다. 가비지 컬렉션(Garbage Collection)에 대해서 알 필요는 없지만, 간단하게 설명하면 이렇습니다. C에서는 free() 함수를 통해서 수동으로 해제해줘야 합니다. 하지만, Java나 .NET은 가비지 컬렉터가 세대(Generation)를 구분해서 더이상 사용되지 않는다고 판단될 때 자동으로 메모리를 해제시켜줍니다.

     

    일반적인 간단한 프로그램의 경우에는 가비지 컬렉션이 자동으로 메모리를 관리해주기 때문에 메모리 누수가 발생할일은 거의 없습니다. 하지만, 거대하고 복잡한 엔터프라이즈급 프로그램에서는 메모리도 개발자가 신경써줘야 합니다. 닷넷에서는 IDisposable 인터페이스를 상속 받아서 수동으로 메모리를 해제할 수 있도록 구현해야 합니다. 그리고, IDisposable 인터페이스를 구현하고 있는 클래스들은 Dispose를 호출해서 메모리에서 삭제해야 합니다.

    internal class QueueTask : IDisposable

     

    인터페이스를 명시적으로 구현합니다.

            public void Dispose()
            {
                Dispose(disposing: true);
                GC.SuppressFinalize(this);
            }

     

    실제 Dispose 구현부에서는 인스턴스에서 사용된 객체들을 모두 메모리에서 해제시킵니다.

    this.backgroundTask.Dispose();
    this.backgroundImage.Dispose();
    this.ScriptEngine.Dispose();

     

    참고로, Close와 Dispose를 모두 구현하는 클래스들도 있는데요. Dispose보다 Close가 상위에 있습니다. 일반적으로 Close를 호출하면 자동으로 Dispose도 호출됩니다. 이제 다시 변수로 돌아와서 속성 데이터가 IDisposable을 구현하고 있으면 아래와 같이 Dispose를 호출해서 메모리에서 제거해야 합니다. 제거하지 않고 새로운 데이터를 할당하면 이전 데이터는 삭제할 방법이 없습니다. 해당 객체를 참조하고 있는 메모리 주소를 잃어버리기 때문입니다.

                            if (property != null)
                            {
                                object? disposer = property.GetValue(action);
    
                                if (disposer != null && disposer is IDisposable)
                                    ((IDisposable)disposer).Dispose();

     

    사용자 편의를 위해 어쩔 수 없이 변수의 데이터는 String으로 되어 있습니다. 일반적인 텍스트뿐만 아니라 좌표, 크기, 사각형등등... 다양한 형식의 자료형을 쉽게 처리하기 위함입니다. 데이터에 "홍길동"을 넣을수도 있지만, "100, 200"과 같은 좌표 또는 크기를 넣을수도 있습니다. 이외에도 다양한 형식의 오브젝트를 추가할 수 있기 때문에 형 변환에 대한 로직이 복잡해지는 문제를 회피할 수 없습니다.

     

    아래 코드는 문자열을 좌표 형식인 Point로 변환해주는 도우미 메소드입니다. 이외같은 메소드를 많이 만들어야 합니다.

            public static Point ConvertPoint(string point, out string message)
            {
                message = string.Empty;
    
                if (string.IsNullOrEmpty(point))
                {
                    message = new ArgumentNullException().Message;
                    return new Point();
                }
    
                try
                {
                    string[] coords = point.Split(',');
                    int x = int.Parse(Regex.Replace(coords[0], @"\D", string.Empty));
                    int y = int.Parse(Regex.Replace(coords[1], @"\D", string.Empty));
    
                    return new Point(coords[0].Contains("-") ? x * -1 : x, coords[1].Contains("-") ? y * -1 : y);
                }
                catch (Exception ex)
                {
                    message = ex.Message;
                    return new Point();
                }
            }

     

    최종적으로 특정할 수 없는 클래스들은 아래와 같이 Convert.ChangType 메소드를 사용해서 변경할 수 있습니다.

        property.SetValue(action, Convert.ChangeType(variableValue, property.PropertyType));

     

    이렇게 변수로부터 가져온 데이터를 액션의 속성에 추가하는 방법을 알아봤습니다. 아래 코드는 액션의 속성 값을 변수에 추가하는 메소드인데요. 대부분 동일하지만, 훨씬 더 간단하게 처리할 수 있습니다. 액션의 속성은 어떤 타입을 가지지만, 변수는 모든 타입을 저장하기 위해 최상위 클래스인 object로 되어 있기 때문입니다.

     

    GetVariableProcess와 동일하게 변수 관련 액션은 로직에서 제외시켰습니다.

            private void SetVariableProcess(ActionModel action)
            {
                if (action is Model.Action.Variables.AddVariableModel)
                    return;

     

    핵심 코드는 아래와 같습니다. 액션의 속성과 변수가 존재하면 변수 목록에 속성의 값을 저장해줍니다. 위에서도 언급했듯이 모든 개체의 최상위 클래스인 object로 되어 있기 때문에 저장하는건 간단합니다. 하지만, object로 저장된 개체가 어떤 형식인지 알 수 없기 때문에 반대로 가져오는건 코드가 복잡해지는겁니다.

                        if (variables.ContainsKey(item.Name))
                        {
                            var property = GetProperty(action, item.PropertyName);
    
                            if (property != null)
                            {
                                object? propertyValue = property.GetValue(action);
                                variables[item.Name] = propertyValue ?? string.Empty;
                            }
                        }

     

    이제 완성된 프로그램을 테스트 해볼까요? 아래 동영상과 같이 a 변수를 만들고, 장치 입력 방법 액션 2개를 추가했습니다. 두번째 액션에서는 a 변수에 자신이 설정한 값을 저장하고, 세번째 액션에서는 a 변수에 저장되어 있는 값을 가져옵니다. 매크로를 실행하고 나면 두번째 액션의 설정된 값이 세번째 액션에 적용된걸 확인할 수 있습니다.

     

     

    변수에 대한 개념이 일반인들이 이해하기에 어려운 부분이 있습니다. 일반적으로 사용되는 용어도 아닌데다가 이런식의 사고(思考)를 할일이 없기 때문입니다. 가능하면 개발자가 아닌 일반인들이 쉽게 이해할 수 있는 용어로 변경하려고 했으나, 마땅한 용어도 없고 인터넷 검색하면 수많은 정보가 나오는 변수라는 이름을 버리기에 리스크가 크다고 판단했습니다.

     

    대부분의 용어들은 인터넷에서 검색만 해봐도 블로그나 티스토리에 자세하게 설명된 수많은 정보가 존재합니다. 자신의 컴퓨터 상태도 마찬가지고요. 검색만 잘해도 많은 시간을 단축할 수 있고 다양한 정보들을 통해서 한단계 성장할수도 있습니다. 물론, 검색도 요령이 필요하지만요^^

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.