NGMsoftware

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

    학습


    C# C# .NET 매크로 프로그램 만들기. (기계식 또는 하드웨어 방식 마우스 매크로 with Class DD)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 매크로는 크게 2가지로 분류가 됩니다. 일반적인 매크로는 윈도우 API를 이용해서 소프트웨어 신호를 프로그램 또는 윈도우에 입력합니다. 컴퓨터에 연결된 마우스와 키보드 장치(Device)가 아닌 논리적인 API 신호라서 일부 프로그램에는 마우스와 키보드 입력 신호가 동작하지 않습니다. 그래서, 아주 오래전부터 하드웨어 방식으로 마우스와 키보드 신호를 자동화 하려는 노력들이 있어왔습니다.

     

    엔지엠 매크로도 초창기에는 소프트웨어 신호만 처리할 수 있었는데요. 엔지엠 매크로 5부터 클래스 디디(Class DD)와 같은 기능을 도입하기 시작했습니다. 현재는 클래스 디디와 인터셉션, 아두이노등등... 다양한 방식으로 하드웨어 입력을 처리할 수 있습니다. 이전 시간에 하드웨어 또는 기계식 마우스와 키보드 신호로 변환해주는 아두이노를 개발했는데요. 이와 관련된 내용은 아래 글을 참고하시면 됩니다.

    C# .NET 매크로 프로그램 만들기. (기계식 또는 하드웨어 방식 마우스 매크로 1부) ]

    C# .NET 매크로 프로그램 만들기. (기계식 또는 하드웨어 방식 마우스 매크로 2부) ]

    C# .NET 매크로 프로그램 만들기. (기계식 또는 하드웨어 방식 마우스 매크로 3부) ]

     

    오늘 같이 만들어볼 기능은 클래스 디디입니다. 보통 디디라고 부르는 라이브러리인데요. 이 기능을 개발하려면 디디 라이브러리 또는 모듈을 미리 다운로드 받아야 합니다.

    [ 클래스 디디 다운로드 ]

     

    클래스 디디를 다운로드 받았으면 압축을 해제하고, 샘플 코드를 살펴봅시다. 아래와 같이 다양한 언어들로 예제가 준비되어 있습니다.

    TQa2e9q.jpeg

     

     

    제가 하는 언어는 닷넷이기 때문에 저 예제 코드를 참고하면 되는데요. 만약, 다른 언어로 개발하시는 분들은 해당 언어 예제를 참고하면 될듯 합니다. 디디 모듈을 로딩하고, 해제하는 코드는 아예 다 코딩이 되어 있다보니 딱히 뭔가 건드릴건 없습니다. 그냥 그대로 가져다 사용하면 됩니다. 참 쉽죠?

     

    요즘은 개발하기 정말 편한 세상이 되었습니다. 누구나 약간만 공부하면 프로그램을 만들 수 있게 되었으니까요. 그런데다가 Chat GPT의 등장으로 인공지능까지 합세하면 몇년후에는 정말 개발자가 필요 없어지는 세상이 올지도 모르겠어요. 이제는 남녀노소 누구나 약간의 시간만 들이면 대부분의 프로그램은 인공지능이 다 만들어줄거 같아요.

     

    아래는 CDD 전체 코드예요.

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace App_csharp
    {
        public partial class Form1 : Form
        {
            private CDD dd;
    
            public Form1()
            {
                InitializeComponent();
            }
     
            private void Form1_Load(object sender, EventArgs e)
            {
                this.button_start.Enabled = false;
    
                reg_hotkey();                            // 注册热键
    
                dd = new CDD();
            }
    
            private void Form1_FormClosing(object sender, FormClosingEventArgs e)
            {
                unreg_hotkey();
            }
            
            private void timer1_Tick(object sender, EventArgs e)
            {
                if (radioButton1.Checked == true)     
                {
                    int ddcode =  300; //tab == 300 in ddcode
                    dd.key(ddcode, 1); //1=down
                    System.Threading.Thread.Sleep(50);           //may, delay 50ms
                    dd.key(ddcode, 2); //2 = up
                    return;
                }
    
                if (radioButton2.Checked == true)   
                {
                    //'1==L.down, 2==L.up, 4==R.down, 8==R.up, 16==M.down, 32==M.up
                    dd.btn(1);
                    System.Threading.Thread.Sleep(50);           //may, delay 50ms
                    dd.btn(2);                                    
                    return;
                }
                            
                if (radioButton3.Checked == true)   
                {
                    dd.movR(20, 20);   //move rel.
                    dd.mov(200, 200); //move abs.
                    return;
                }
                       
                if (radioButton4.Checked == true) 
                {
                    dd.whl(1);                                  //up
                    System.Threading.Thread.Sleep(1000);
                    dd.whl(2);                                  //down
                }
    
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                OpenFileDialog ofd = new OpenFileDialog();
                ofd.Filter = "DD|*.DLL";
    
                if (ofd.ShowDialog() != DialogResult.OK)
                {
                    return;
                }
    
                //'x86 -> * 32.dll
                //'x64 -> *.64.dll
                //'AnyCpu -> Error
                //Maybe not run in vs, make to exe for test.
                LoadDllFile(ofd.FileName);
            }
    
            private void button2_Click(object sender, EventArgs e)
            {
                if (button_start.Text == "Start")
                {
                    button_start.Text = "Stop";
                    timer1.Enabled = true;
                }
                else
                {
                    button_start.Text = "Start";
                    timer1.Enabled = false;
                }
            }
          
            private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
            {
                string url = "http://" + linkLabel1.Text;
                System.Diagnostics.Process.Start(url);
            }
    
            private void LoadDllFile(string dllfile)
            {
                label1.Visible = false;
                label2.Visible = false;
                button_start.Enabled = false;
    
    
                int ret = dd.Load(dllfile);
                if (ret !=1) { MessageBox.Show("Load Error"); return; }
    
    
                ret = dd.btn(0); //DD Initialize
                if (ret != 1) { MessageBox.Show("Initialize Error"); return; }
    
                button_start.Enabled = true;
                label1.Visible = true;
                label2.Visible = true;
    
                textBox1.Text = dllfile;
    
                return;
            }
    
    
            #region "HotKey"
            [DllImport("user32.dll")]
            public static extern bool RegisterHotKey(
             IntPtr hWnd,
             int id,                            
             KeyModifiers modkey,    
             Keys vk                         
            );
            [DllImport("user32.dll")]
            public static extern bool UnregisterHotKey(
             IntPtr hWnd,              
             int id                          
            );
    
            void reg_hotkey()
            {
                RegisterHotKey(this.Handle, 80, 0, Keys.F8);
                RegisterHotKey(this.Handle, 90, 0, Keys.F9);
            }
    
            void unreg_hotkey()
            {
                UnregisterHotKey(this.Handle, 80);
                UnregisterHotKey(this.Handle, 90);
            }
    
            protected override void WndProc(ref Message m)
            {
                const int WM_HOTKEY = 0x0312;                      
                switch (m.Msg)
                {
                    case WM_HOTKEY:
                        ProcessHotkey(m);                                     
                        break;
                }
                base.WndProc(ref m);
            }
    
            private void ProcessHotkey(Message msg)        
            {
                switch (msg.WParam.ToInt32())
                {
                    case 80:
                        Fun80();
                        break;
                    case 90:
                        Fun90();                                                        
                        break;
                }
            }
    
            private void Fun80()
            {
                dd.str("Keyboard char [A-Za_z] {@$} ");
            }
    
            private void Fun90()
            {
                if (dd.key != null)
                {
                    //CTRL+ALT+DEL
                    dd.key(600, 1);                                      //600 == L.CTRL down
                    dd.key(602, 1);                                      // ALT   down
                    dd.key(706, 1);                                      // DEL   down
                    System.Threading.Thread.Sleep(5);
                    dd.key(706, 2);                                       //up
                    dd.key(602, 2);
                    dd.key(600, 2);
                }
            }
    
            #endregion
    
        }
    }

     

    이제 밥상은 다 차려졌으니 숟가락과 젓가락만 들고, 맛있게 먹으면 됩니다. 일단, 모듈을 로딩해야 하니 클래스 디디 연결 액션에 아래와 같이 파일 선택 속성을 만들어줍니다.

    [LocalizedCategory("Action")]
    [LocalizedDisplayName("SelectModule")]
    [LocalizedDescription("SelectModule")]
    [Browsable(true)]
    [DefaultValue(null)]
    [Editor(typeof(TypeEditor.OpenFileSelectorEditor), typeof(UITypeEditor))]
    public string? SelectModule { get; set; }

     

    디디는 아두이노와 다르게 마우스가 굳이 이동할 필요는 없습니다. 그렇더라도 하드웨어 방식은 동일하게 처리할 수 있도록 아래와 같이 마우스 이동 속도와 거리를 추가할께요.

    [LocalizedCategory("MouseSpeed")]
    [LocalizedDisplayName("Speed")]
    [LocalizedDescription("MouseSpeed")]
    [Browsable(true)]
    [DefaultValue(0)]
    public int Speed { get; set; }
    
    [LocalizedCategory("MouseSpeed")]
    [LocalizedDisplayName("Distance")]
    [LocalizedDescription("MouseDistance")]
    [Browsable(true)]
    [DefaultValue(0)]
    public int Distance { get; set; }

     

    마우스 이동 기능을 사용할지 여부도 사용자가 결정하는게 좋겠네요.

    [LocalizedCategory("Action")]
    [LocalizedDisplayName("UseMouseMove")]
    [LocalizedDescription("UseMouseMove")]
    [Browsable(true)]
    [DefaultValue(false)]
    public bool AutoCorrection { get; set; }

     

    디디를 초기화 해주는 코드입니다.

    var dd = new Ai.Api.HardwareInputManager.CDD();
    int ret = 0;
    
    try
    {
        ret = dd.Load(SelectModule);
    }
    catch (AccessViolationException ex)
    {
        player.Manager.Output.WriteLine($"[ClassDD] {ex.Message}", log4net.Core.Level.Fatal);
        return id;
    }
    
    if (ret != 1)
    {
        player.Manager.Output.WriteLine($"[ClassDD] {player.Manager.Client.ResxMessage.GetString("ModuleLoadFail")}", log4net.Core.Level.Error);
    
        if (player.Manager.ClassDD != null)
        {
            player.Manager.ClassDD.Dispose();
            player.Manager.ClassDD = null;
        }
    
        return id; 
    }

     

    디디도 아두이노처럼 글로벌하게 동작해야 합니다. 최초 한번 설정하면 이 후 모든 액션들이 이 설정을 따라가야 하니까요.

    player.Manager.ClassDD = dd;
    player.Manager.ClassDDMouseSpeed = this.Speed;
    player.Manager.ClassDDAutoCorrection = this.AutoCorrection;
    player.Manager.ClassDDMouseDistance = this.Distance;
    player.Manager.ClassDDMouseDownUpDelay = this.MouseDownUpDelay;
    player.Manager.ClassDDKeyboardDownUpDelay = this.KeyboardDownUpDelay;

     

    이제 각각의 마우스 동작에 디디 관련 내용들을 추가 해주면 작업은 끝입니다. 디디 샘플 코드의 내용을 참고해서 자신의 프로그램에 맞게 추가하세요. 참고로, 디디는 X1, X2 버튼은 지원하지 않기 때문에 사용자에게 메세지를 표시하는걸로 했습니다.

    switch (button)
    {
        case Api.MouseKeyboardManager.MouseSimulator.MouseButton.Left:
            player.Manager.ClassDD.btn(1);
            Task.Delay(mouseDownUpDelay.Value).Wait();
            player.Manager.ClassDD.btn(2);
            break;
        case Api.MouseKeyboardManager.MouseSimulator.MouseButton.Right:
            player.Manager.ClassDD.btn(4);
            Task.Delay(mouseDownUpDelay.Value).Wait();
            player.Manager.ClassDD.btn(8);
            break;
        case Api.MouseKeyboardManager.MouseSimulator.MouseButton.Middle:
            player.Manager.ClassDD.btn(16);
            Task.Delay(mouseDownUpDelay.Value).Wait();
            player.Manager.ClassDD.btn(32);
            break;
        case Api.MouseKeyboardManager.MouseSimulator.MouseButton.X1:
            player.Manager.Output.WriteLine($"[X1 Click] {player.Manager.Client.ResxMessage.GetString("NotSupportOption")}", log4net.Core.Level.Warn);
            break;
        case Api.MouseKeyboardManager.MouseSimulator.MouseButton.X2:
            player.Manager.Output.WriteLine($"[X2 Click] {player.Manager.Client.ResxMessage.GetString("NotSupportOption")}", log4net.Core.Level.Warn);
            break;
    }

     

    전부다 코딩을 완료했다면 이제 테스트를 해보면 됩니다. 테스트에 사용할 그림판을 실행하세요.

    tziiifk.jpeg

     

     

    그리고, 아래와 같이 클래스 디디 연결 액션과 마우스 클릭 액션을 하나씩 추가해줍니다.

    RVBUWXw.jpeg

     

     

    디디 연결 액션에서 라이브러리를 선택해야 하는데요. 디디 홈페이지의 가이드에 보면 x64.dll을 붙여서 사용하라고 되어 있더라고요. 우리도, 이걸 일단 붙여서 테스트를 해보면 될거 같습니다. 마우스 클릭 액션에서 좌표를 그림판으로 설정하고 실행 해보세요. 클릭은 정상적으로 될겁니다.

     

    그런데, 이게 소프트웨어 신호인지 하드웨어 신호인지 확인은 안되잖아요? 그래서, 소프트웨어 신호가 막힌 게임이나 어떤 프로그램으로 테스트를 하는게 좋습니다. 제 경우에는 이미 엔지엠 6 버전에서 테스트를 했었던거라서 그냥 그림판에서 진행하도록 할께요.

     

    아래와 같이 장치 입력 방법을 클래스 디디로 선택하세요.

    0SR2LTq.jpeg

     

     

    이제 정상 동작하는지 테스트를 해볼까요? 마우스 클릭부터 실행할께요.

     

     

    잘 동작하는군요. 이번에는 마우스 드래그도 해볼께요.

     

     

    드래그도 잘 됩니다. 이외에도 FPS 게임에서 주로 사용하는 Relative 기능도 정상 동작하는지 체크했는데요. 절대 좌표가 아닌 상대 좌표로 계산해서 이동하기 때문에 프로그램 중앙을 기준으로 좌표를 설정해야 합니다. 예를 들어서 좌우 이동의 경우 X 좌표 값만 중앙을 기준으로 플러스, 마이너스로 설정해야 해요. 그리고, 상하를 이동하는 Y 좌표는 0으로 설정해야 합니다.

     

    이제 인터셉션 관련해서 개발을 해야 할텐데요. 하드웨어 방식도 상대 좌표를 모두 지원하긴 하지만, 테스트할만한 프로그램이 없어서 보여드릴 수가 없네요. 하지만, FPS 게임과 같은 곳에서도 사용하시는 분들이 문제 없다고 하시는거 보면 잘 동작하는듯 합니다. 나중에 기회가 되면 테스트를 한번 해볼께요.

     

    개발자에게 후원하기

    MGtdv7r.png

     

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

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

    감사합니다~

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

    댓글목록

    등록된 댓글이 없습니다.