[C#] C# DLL이 아닌 DLL파일 연결하여 사용하기

가우스메터 라는 장비와 연동하는 프로그램을 만들어야 한다

오랫만에 회사에서 연락왔다. 앞뒤사정 다 생략하고 결론부터 말하면 역시 뭔가 해달라는거였다. 가우스메터? 라는 장비랑 연동하여 주기적으로 값을 읽어 그 수치가 일정 이상일 경우 생 X랄발광을 하는 프로그램을 만들어야 한다. 물론 데이터 저장은 기본이고... 지X발광 하는거야 어렵지 않다고 생각했고, 데이터 저장도 별로 어렵지 않다고 생각했다. 늘 하는 방식이 있으니까. 근데 문제는 이 장비와의 통신은 USB여야 한다는 것.

사실 나는 프로그래머라기 보다는 그냥 인터넷에 있는 코드를 복붙해와 적당히 수정하고 적당히 작동되게 하는 그냥 그런 사람인데, USB 통신을 좀 도와달라는 부탁에 차마 거절은 못하고 일단 가서 확인해보겠다고 했다.


DLL 파일이 있긴 한데, C#에서 열리지 않는다.

해당 장비의 제조사에서 프로그램 개발하라고 DLL 파일을 제공했다. 일단 이걸 사용법을 알아야 하는데, 내가 알고 있는 사용법은 참조에 DLL파일을 넣는 것 정도... 당연히 그 방법을 먼저 시도했다. 안된다.

명세서를 보니 이 DLL파일은 C++로 만들어진거라 C나 C++(MFC), VB에서 제대로 작동하는듯, 예제 프로그램도 엑셀에 포함된 VB 메크로로 작동한다.

물론 DLL파일만 덩그러니 있고 나머지는 설명서 뿐이다. 뭐 드라이버류도 있고 한데 일단 개발에 필요한건 DLL파일이고 이를 사용할 방법인데..


DllImport("file")

기존 C++ 프로젝트들과 호환성을 고려하기 위한것인지, C#에는 이런게 있다. 바로 외부 DLL 파일을 코드에서 동적으로 활당하게 하는것. 물론 이것만 쓰면 안되고, 이 DLL 안에 있는 함수도 같이 정의해서 C# 프로젝트에서도 쓸 수 있게 해야 한다. 그러면 그 함수는 어디서 얻냐. 라고 하면, DLL 파일을 개발하라고 제공하면 그 DLL에서 쓸 수 있는 함수와 설명이 있는 문서도 같이 제공하니 그걸 참고하면 된다.


  1. 즉, 먼저 DLL파일과 설명서를 준비하고 설명서를 먼저 본다. 예시는 가우스메터 장비와 통신하기 위해 필요한 gm0.dll이라는 DLL 파일과 그의 설명서이다. 아래와 같이 문서 안에 어떤 함수가 있고 어떤 기능을 하는지가 정확히 적혀 있다. 개발환경에 맞는 자료형으로 만들어져 있을테니 당연히 C#에 맞는 자료형으로 적당히 수정한다.


  2. DLL파일은 프로그램의 exe 파일과 같은 경로안에 준비한다. 프로젝트 안에 넣으면 어떻게 될지 모르겠는데 내 생각에는 안될 것 같다. 그리고 위의 설명서에서 사용하고자 하는 함수를 보고 그에 맞춰 적는다. 예시에서는 장비와의 새로운 커낵션을 만드는 gm0newgm, 장비와 연결하는 gm0startconnect, 장비에서 값을 읽어오는 gm0getvalue 라는 함수 3개를 사용할 것이다. DllImport를 사용하려면 System.Runtime.InteropServices; 를 using 해야 하고, 클래스 선언후 밑에, 메소드 밖에 써주면 된다. 함수의 접근지정자도 맘대로 해도 된다. 단 static extern으로 선언해야 한다.(생각해보면 당연하다)


    using System.Runtime.InteropServices;
    
    [DllImport("gm0.dll")]
    private static extern int gm0_newgm(int port, int mode);
    
    [DllImport("gm0.dll")]
    private static extern int gm0_startconnect(int hand);
    
    [DllImport("gm0.dll")]
    private static extern double gm0_getvalue(int hand);
    
  3. 이제 원하는곳에서 해당 함수를 쓰고 동작을 확인하자. 아래 예제에서는 ERROR:-2 라고 표시됬는데 이는 장비와 연결할 수 없을때 나오는 에러 코드 중 하나이니 결과적으로 DLL파일의 연결은 잘 되었다고 할 수 있다. 만약 DLL파일이 없다면, GDI+ 예외가 뜨거나 아까 using으로 사용한 System.Runtime.InteropServices 예외가 뜨니, 이를 적절히 처리하면 될 것이다.



Minny_

,

[C#] 시리얼 통신 시, 수신 이벤트가 두번 이상으로 들어올 경우


이거 때문에 하루종일…

현장 실습에서 젤 어이가 없으면서 젤 당황스러웠던게 바로 시리얼 통신이다. 산업 현장에서 측정장비나 생산장비들이 가지는 정보를 PC나 다른 장비로 전달하기 위해서 시리얼 통신이 많이 사용되는데, 사실 시리얼 통신이라는것을 이때 처음 본 것이다. 기껏 본거야 안드로이드 폰에 펌웨어 올릴 때 가상 COM포트로 데이터 전송하는것 정도? 현장 실습이 반이상 지나서 시리얼 통신을 주구장창 만저본 결과, 뭐 정말 별 것도 아니었지만. 컴퓨터를 처음 만질때 부터 USB에 키보드와 마우스를 꼽았던 나로써는 솔직히 이게 뭔지… 조차 몰랐으니까.


처음 봤으니 당연히 이걸 어떻게 다뤄야 하는지에 대한 감 조차 오지 않았다. 근데 하라고 하니 일단은 해야지. 그래서 처음에는 진짜 아무것도 모르고 시리얼 통신 예제를 복붙하다시피 해서 요구사항대로 진행했었는데, 정말 얘기치 않은 문제가 발생하는것이다. 열심히 예제와 실제 통신하는것을 찾아본 결과 개념 자체는 어느정도 이해가 됬는데, 정작 프로그램에 적용해보니 뭔가 이상한 것이다.

결국 하루종일 그 문제에만 몰두했다. 이게 도대체 뭔지조차 감이 오지 않았고, 당장 시리얼 통신을 시물레이션할 수 있다는 것도 알지 못했으니 결국 실물로 측정기같은게 있어야 계속 진행할 수 있었는데 그걸 전혀 몰랐다. 그래서 원인을 사실 전혀 알지 못했었다. 뭔가 이상해서 안되긴 하는데 뭐가 문제인지를 전혀 몰랐다.


시물레이션을 해보니 알게 되었는데…

결국 시리얼 통신에 대해 깊이 알아보게 되었고, 시물레이션이 가능한 가상 시리얼 통신 시물레이터도 알게 되었고, 결과적으로 쓰는 방법 자체를 완벽히 알게 되었다. 그래서 테스트를 해 보니…

수신되는 데이터가 짤려 들어오는것이다


여러 C# 시리얼 통신 예제들은 수신시 이벤트가 발생하도록 되어 있다. 이 시리얼 통신의 수신 이벤트가 발생하면 수신 버퍼에서 데이터를 가져와 처리를 하게 된다. 문제는 이 이벤트가 한번 데이터 송신시 한번 되는것이 아닌, 두세번에 걸쳐 나눠 들어오는 것이다. 간단한 예를 들어보면…

동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라 만세
무궁화 삼천리 화려강산 대한사람 대한으로 길이 보전하세

라는 문자열 데이터를 시리얼 통신으로 받으면


동해물과 백두산이 마르고 닳도록 하느님이 보우하
사 우리나라 만세
무궁화 삼천리 화려강산 대한사람 대
한으로 길이 보전하세

라고 세번의 이벤트가 발생되고 이걸 합쳐야 원래의 데이터로 들어오는것이다. USB to RS232 통신이여서 그런건지, 아니면 C#에서 제공하는 시리얼 통신 예제의 문제인지, 아니면 송신하는 쪽의 문제인건지, 통신 설정의 문제인지는 정확히 알 수 없지만. 여튼 데이터가 나눠진다. 나눠지는 횟수나 데이터가 잘려지는 위치는 그때그때 랜덤하며, 이게 결국 비트로 들어오는 데이터다보니 합치는것도 잘 합쳐야 데이터가 깨지지 않는다. 그리고 아무리 잘 들어와도 조금씩 데이터가 깨지는건 덤


문제는, 내가 필요로하는건 측정 장비에서 송신하는 데이터를 모두 받은 다음 아스키 코드로 번역하여 문자열로 만들고, 완성된 문자열에서 특정 값들을 가져오는것. 가져와서 DB에 쏴주고 특정 값은 HTTP POST로 보내줘야 한다.

그래서 어떻게 할까 고민을 했었는데, 결과적으로는 야매스러운 방법이긴 했지만, 완성을 하였다.


데이터 수신을 받아 처음과 끝으로 구분

내가 한 방법은 이 소제목과 같다. 측정 장비는 정규화된 문자열을 보내준다. 그중에서 값 부분만 측정한 값을 채워서 보내주는 것이다. 예를 들어 전자저울 같으면,

''''''''''''''''''''
측정 정보
''''''''''''''''''''
측정 모델       어쩌구
측정 시간       저쩌구 s
측정 타입       어쩌구 type

''''''''''''''''''''
측정 결과
''''''''''''''''''''
최종 무개       저쩌구 kg

''''''''''''''''''''
측정 종료
''''''''''''''''''''

이런식으로 되어 있다는 것. 물론 원래 전자저울은 그냥 00.00KG. 딱 한줄 찍어 보내준다. 이건 그냥 예시를 위해…


그러니까 여기서 시작은 “측정 정보” 라는 문자열로, 끝은 “측정 종료” 라는 문자열로 구분할 수 있다는 것. 시리얼 통신을 쭈욱 받아 문자열로 계속 쌓으면서 시작 문자열이 확인이 되면 그 앞은 다 버리고, 그 뒤부터 다시 차곡차곡 쌓는다. 쌓다가 “측정 종료” 라는 문자열을 확인하게 되면 그 앞은 필요한 곳으로 전송하던지 저장하고 있던지 하고, 그 뒤에 들어오는 문자열은 버리는것.

좋은 솔루션이라고는 할 수 없다 하지만, 별다른 해결 방법이 안보이는데 뭐… 정확히는 RS232 통신 쪽 설정 문제인거 같은데, 나로써는 도저히 해결을 할 수 없어 보여 이런 방법이라도 적용해본거지.


코드 및 설명

앞에서 이미 다 설명했으니 관련된 부분의 코드만 간략하게 기록한다. 아무래도 현재 돌아가고 있는 프로그램의 코드고 내 실력이 형편없어 보기 난해할 순 있지만. 그래도 나는 이런식으로 해결했다는 의미로 남기는 것.

int offset = 0;
string startStr;
string endStr;


private void sPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            byte[] rxBuffer;

            if (offset == 0)
            {
                rxBuffer = new byte[4096];
            }

            string rxString = "";
            int intRecSize = sPort.BytesToRead;

            if (intRecSize != 0)
            {
                sPort.Read(rxBuffer, offset, intRecSize);
                offset += intRecSize;

                for (int iTemp = 0; iTemp < offset; iTemp++)
                {
                    rxString += Convert.ToChar(rxBuffer[iTemp]);    
                }

                if (rxString.Contains(startStr))
                {
                    rxString = rxString.Substring(rxString.IndexOf(startStr));
                    if (rxString.Contains(endStr))
                    {
                        program.dataReceived(rxString, "Receive");
                        offset = 0;
                    }
                }
            }
        }

시리얼 통신 예제 자체는 이미 인터넷에 많이 돌아다니고 있다. 다만 이 부분이 해결된 시리얼 통신 예제는 보이지가 않더라. 그러니까 이런 경우가 흔하지는 않다는 거고… 흔하지 않다는건 잘 해 두기만 한다면 이런 문제는 없다는 거겠지. 그래서, 시리얼 통신 자체의 부분은 생략한다. 해당 메서드는 시리얼 통신 예제에서 시리얼 데이터가 수신될 때 발생하는 이벤트 메서드이니 쉽게 찾을 수 있을 것이다.


여기에서 startStr과 endStr 변수는 통신의 시작과 종료에 정해진 문자열을 지정하면 된다. 코드를 간단히 보면 알겠지만 시리얼 통신으로 받는 족족 문자열로 변환한 후 시작 문자열과 종료 문자열을 검사해서 시작 문자열이 있으면 그 이전은 잘라내고, 시작 문자열과 종료 문자열 둘다 있으면, 특별한 작업을 하던지 다른 클래스로 보내던지(여기서는

program.dataReceived(rxString, "Receive");

라는 부분으로 program 클래스에 완성 문자열을 보내게 된다.) 하면 되고, 시작과 끝으로 셋트가 맞춰지면 offset을 0으로 하여 문자열을 초기화 하고 다음 시리얼 통신의 수신 대기를 하게 된다.

이렇게 해서 일단 필요하다고 하는 현장 두곳에다가 넣어놨는데, 지금까지 클래임 없이 조용한걸 보면 아마도 잘 작동하는듯.

Minny_

,

[C#] WPF 프로그램의 노티바(시스템 트레이) 아이콘 생성


그냥 바로 못한다


현장실습 중 만든 프로그램은 기본적으로 작동중에는 백그라운드에서 돌아야 한다. 라는 조건이 붙는데, 아래 글(2017/02/06 - [Tip/Develop] - [C#] WinForm 프로그램의 노티바(시스템 트레이) 아이콘 생성)과 같은 조건이다. 같은 C#이고, 전에 만들어둔 코드를 그대로 붙혀 두면 될 것이라고 쉽게 생각했었는데, 그게 아니었다. 당장 NotifyIcon 이라는 도구상자 컴포넌트가 없다.


이유를 알아보니, NotifyIcon이라는 컴포넌트는 System.Windows.Forms 안에 포함되어 있는 거고, WPF는 Forms를 Using하지 않으니 바로 사용할 수 없는 것. 간단하게 System.Windows.Forms을 Using하면 되지 않을까 라는 생각을 하지만, 그렇게 할 경우 MessageBox와 같은 System.Windows.Forms와 System.Windows.Controls에 동일하게 존재하는 객체들의 모호성이 생긴다.


결국은 그냥 NotifyIcon 객체를 직접 지시하여 생성해 주고 그 설정을 맞춰주면 된다. 사실 WinForm의 방법과 크게 차이는 없지만, 직접 지시를 해줘야 하는 부분이 있어야 한다는것.


배경이 똑같으니 목적이나 방법 또한 아래 글(2017/02/06 - [Tip/Develop] - [C#] WinForm 프로그램의 노티바(시스템 트레이) 아이콘 생성)과 거히 동일하니 뭘 할 건지는 해당 글을 먼저 보고 오는것을 추천.



그래서 하는 방법


매인 윈도우가 있는 클래스에 전역변수로 NotifyIcon을 직접 생성해 주고, init를 할 때 해당 객체를 적당히 맞춰준다. WinForm에서 생성하는것과는 다른 부분이 바로 이 부분이고, 그 뒤의 부분은 동일. 필요한 상황에 맞는 이벤트나 메서드에 NotifyIcon을 보여주고 창을 숨긴다. NotifyIcon에 더블클릭 이벤트를 걸어주고, 이 이벤트 안에는 NotifyIcon을 숨기고 창을 보이게 하는것.


1. NotifyIcon을 생성하고 설정

 public partial class MainWindow : Window{
    public System.Windows.Forms.NotifyIcon notify;

    public MainWindow(){
        InitializeComponent();
    }
    ...


전역으로 하나 생성해주고,


private void Window_Loaded(object sender, RoutedEventArgs e){
     notify = new System.Windows.Forms.NotifyIcon();
     notify.Icon = Properties.Resources.ico;
     notify.Text = "PLC 통신";
     notify.DoubleClick += Notify_DoubleClick;

     ...
}


Window가 로드될 때 이름과 아이콘, 그리고 이벤트를 지정해 준다. WinForm때와 마찬가지로 아이콘은 지정해주지 않으면 아무리 잘해도 보이지 않으니강조하는 이유는 내가 당해봐서… 꼭 빼먹지 말고 해주자.


2. 창이 숨겨지고 시스템 트레이 아이콘이 보이게

3. 시스템 트레이 아이콘이 숨겨지고 창이 보이게


는 사실 WinForm때와 동일하다. 2번의 경우 창이 닫길때나 숨겨야 할 상황의 메서드에 창을 숨기고 notifyIcon을 보이게 하면 되고, 3번의 경우에는 그 반대. 물론 그렇다고 WinForm의 코드를 그대로 복붙해서는 안된다. 창의 숨김, 보이기가 WinForm과는 다르니까 신경써주자.

 private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        { 
            if(SharedKeyword.isRunning == true)
            {
                notify.Visible = true;
                this.Visibility = Visibility.Collapsed;
                e.Cancel = true;
                return;

            }
private void Notify_DoubleClick(object sender, EventArgs e)
        {
            notify.Visible = false;
            this.Visibility = Visibility.Visible;
        }


Minny_

,

[C#] WinForm 프로그램의 노티바(시스템 트레이) 아이콘 생성



간단한것만…


현장실습 중에 만든 프로그램에서 가장 기본이 되어야 하는것이 작동중에는 백그라운드에서 돌아야 한다.


막 메뉴가 추가되거나, 아이콘이 상태에 따라서 바뀌거나 하는것이 아닌 간단한 정도만 구현하면 됬었다. 그러니까 순전히 프로그램이 작동중일때(미들웨어로써 역활을 하고 있을때) 프로그램 종료를 하면 종료가 되는것이 아닌

  1. 창이 숨겨지고 2. 시스템 트레이(노티바)에 띄워지도록하고 3. 노티바 아이콘을 더블클릭하면 다시 창이 보이며 시스템 트레이(노티바) 아이콘은 없어지도록

하는것.



위의 목적대로 만드는 방법


WinForm 프로그램은 만들기 쉽다. 도구 상자에서 NotifyIcon 을 가져와서, 아이콘과 이름을 지정한 후, 필요한 상황에 맞는 이벤트나 메서드에 NotifyIcon을 보이게 하고 창을 숨기면 된다. NotifyIcon의 더블클릭 이벤트를 생성해주고 이 이벤트 안에는 NotifyIcon을 숨기고 창을 보이게 하면 끝.



1.NotifyIcon을 생성한다


그냥 도구상자에서 찾아 끌어오면 된다. 그리고 해당 콤포넌트의 아이콘과 이름을 지정해준다. 귀찮으니 이름은 기본값으로 놔두었다. 아이콘은 없으면 안된다. 아이콘이 없으면 아무리 잘 해놔도 보이지 않는다. 아무 아이콘이나 대충 끌어와 리소스에 넣고 맞춰주자.


2.창이 숨겨지고 시스템 트레이 아이콘이 보이게

위와 같은 목적으로 하려면 프로그램이 종료되지 않게 해야 하므로, 해당 프로그램의 매인 From에 FormCosing 이벤트를 붙이고, 해당 메서드에 다음과 같이 작성하면 된다.


private void MoisturmMeasurmentProgram_FormClosing(object sender, FormClosingEventArgs e){
        if (!btnStart.Enabled)
        {
            e.Cancel = true;
            notifyIcon1.Visible = true;
            this.Visible = false;
            return;
        }
    }


물론 상황에 따라 저 if안에 검사할 조건을 바꿔야 한다. 나 같은 경우는 btnStart라는 버튼을 활성화, 비활성화 하는것에 따라 프로그램의 작동 상황을 구분하니 이렇게 한 것이고… 물론 이렇게 하면 안된다!

FromClosing 이벤트를 취소하기 위해 e.Cancel = true를 하였고, 시스템 트레이(노티바) 아이콘을 보이게 한 후, 이 From을 보이지 않게 한다.


3.시스템 트레이 아이콘이 숨겨지고 창이 보이게

창을 다시 띄우고 싶을때 해당하는 메서드에 동작을 넣어주면 된다. 소제목도 2번과 반대이고 로직또한 2번과 반대이니 어렵지 않다. 나는 간단하게 시스템 트레이 아이콘을 더블클릭하면 해당 동작이 되게 한다. 그러기 위해서는 시스템 트레이(노티바) 아이콘의 더블클릭 이벤트를 붙혀주면 된다.


이벤트 붙이는거야 뭐 속성에서 이벤트를 더블클릭해서 메서드 생성을 해주던지, 직접 코드에서 붙혀주던지 알아서 하면 된다. 아래는 해당 이벤트 코드이다.

private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e){
        this.Visible = true;
        this.Activate();
    }



Minny_

,

[App] Viper4Android Installer


주의

이 앱은 루트 권한을 필요로 합니다. 또한 앱 사용 도중 발생하는 모든 피해는 제작자가 책임지지 않습니다.

스크린샷



NC Factory X Over Imagine
[Viper4Android 올인원 인스톨러]


설명

Viper4Android를 기기에 맞게 찾고, 설치해줍니다.

지원 기기에 한해 리커버리 플래싱을 지원하며 (구글 넥서스 5X, 6P) 지원하지 않는 기기에 대해선 APK파일 설치를 지원합니다.

리커버리 플래싱에 사용되는 파일은 기존 V4A AIO Installer r3가 제공됩니다.
(http://overimagine.tistory.com/6)

Viper4Android를 사용하면서 프로세싱 (Processing)이 되지 않을 때를 위한 SELinux 상태 확인 기능이 추가되어 쉽고 빠른 확인이 가능합니다.

피드백은 언제나 환영하며, 문제 발생시 Official.NCF@gmail.com으로 오류, 기기지원 추가요청을 해주시면 감사하겠습니다.

지원 기기

지원 기기 : 구글 넥서스 5X, 넥서스 6P

지원 예정 : 구글 넥서스 5, LG Optimus G Pro (CM12.1)

다운로드

구글 플레이 : https://play.google.com/store/apps/details?id=com.tistory.overimagine.viper4androidinstaller

V4A AIO Installer r3 : http://overimagine.tistory.com/6

DesignOZ

,

올인원 Viper4Android 인스톨러 (V4A AIO Installer)


V4A Installer가 앱으로 출시되었습니다. 자세한 사항은 여기를 확인해주세요!

http://overimagine.tistory.com/14


경고 (Disclaimer)

이 패치를 설치함으로 발생하는 기기의 문제는 필자가 책임지지 않습니다. 이 패치파일은 테스트중입니다.

백업을 생활화합시다.

I’m not responsible for anything that may happen to your N5X as a result of installing this v4a installer. you do so at your own risk and take the responsibility upon yourself. test in progress.


설명 (About)

넥서스 5X에서 Viper4Android를 설치하기 쉽게 올인원(All in One) 형태로 만들었습니다. 넥서스 5X를 위해 만들었지만 기기의 특성을 타는 설정이 없기 때문에 Android 5.1 롤리팝과, 6.0 마시멜로우 기반의 모든 롬에서 사용 가능할 것이라 생각됩니다. 테스트중입니다.

  • V4A 설치
  • MusicFX 제거
  • Busybox 설치
  • V4A 이펙트 구문이 포함된 audio_effects.conf로 교체
  • deepbuffer 영역이 제거된 audio_policy.conf로 교체
  • 미디어 서버 접근 스크립트 추가
  • SELinux Permissive 모드 스크립트 추가

This file is to easily install Viper4Android for N5X.
I made it for N5X, but I think it can to install on another LP, MM roms.

  • install V4A (not priv-app)
  • remove MusicFX
  • install Busybox
  • replace audio_effects.conf (added v4a effect)
  • replace audio_policy.conf (remove deepbuffer)
  • add allow mediaserver to recognize v4a
  • add Selinux Permissive mode script

설치 방법 (Installation)

TWRP 또는 CWM으로 플래싱합니다.

Flashing with TWRP or CWM.

생성일자 (Created)

[Ver. 3]
2015년 12월 26일
December 26, 2015

[Ver. 2]
2015년 11월 28일
November 28, 2015

Thanx to

zhuhang (V4A)
meefik (busybox)
flar2 (Elemental X)
arush@NAVER - first tester
you.

다운로드 (Download)

V4A_Install_Test_3rd.zip


DesignOZ

,