2017년 12월 27일 수요일

Ubuntu 장비의 고정 IP(static IP) 설정법





임베디드 장비이든 혹은 웹 서버이든 IP를 고정해야 할 경우들이 있다.
DHCP로 IP를 자동 할당 받는 경우 IP가 고정이 아닌 유동적으로 바뀔수가 있기 때문에 고정 IP(static IP) 설정하는 법은 여러 모로 필요한 작업이다.
본 포스트에서는 Linux Ubuntu 상에서 IP를 어떻게 고정으로 할당할수 있을 것인지를 정리하고자 한다.
IP를 고정시키기 위해서는 다음의 정보가 필요하다.

-. 고정시킬 IP Address
-. Net mask
-. Default gateway
-. DNS Server

여기서 외부로 나갈 필요가 없는 상황이라면 즉 인터넷을 할 필요가 없이 임베디드 장비와 PC간의 통신만 되면 된다거나 아무튼 외부 통신을 필요로 하지 않는다면 gateway 정보나 DNS server 정보는 없어도 된다.

우선 현재의 네트워크 자원들에 대해 확인부터 해 보자.

ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:ff:a4:58 brd ff:ff:ff:ff:ff:ff

ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:ff:a4:58 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 83702sec preferred_lft 83702sec
    inet6 fe80::c33f:ad86:8225:3597/64 scope link
       valid_lft forever preferred_lft forever

위의 정보를 보면 2개의 네트워크 interface가 보인다. 하나는 lo인데 이는 loop back으로 자기 자신을 나타내는 interface이다.
우리가 고정 IP를 설정할 네트워크는 enp0s3로 명명되어진 이 interface이다.
link/ether는 이 장비가 이더넷 장비라는 뜻이고 IP 주소는 10.0.2.15이다.
참고로 위의 정보는 Virtual Box에서 실행되는 Ubuntu이다.
보통 공유기 하위에 물려 있는 경우라면 192.168.x.x와 같은 형태가 될 것이다.

이제 IP정보를 설정해 보자. 아래 파일을 열어 보면 대체로 이런 정보가 들어 있다.

vi /etc/network/interfaces

# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback

위의 파일 내용에 고정 IP 할당에 대한 정보를 아래와같이 입력하고 파일을 저장한다.

auto enp0s3
iface enp0s3 inet static
address 192.168.0.77
netmastk 255.255.255.0
gateway 10.0.2.2
dns-nameserver 8.8.8.8 8.8.4.4

gateway는 현재의 gateway를 그대로 사용했고 dns-nameserver는 구글 것을 사용했다.
현재의 gateway 정보를 알려면 다음 명령으로 확인할 수 있다.

root@joe-VirtualBox:/etc/network# ip route
default via 10.0.2.2 dev enp0s3  proto static  metric 100
10.0.2.0/24 dev enp0s3  proto kernel  scope link  src 10.0.2.15  metric 100
169.254.0.0/16 dev enp0s3  scope link  metric 1000

현재의 gateway(인터넷을 할수 있는 출입문, 통신이 외부로 나갈수 있는 출입문 IP)정보는
default via 10.0.2.2 dev enp0s3로 되어 있는 10.0.2.2이다.
아래는 또 다른 경우에 대한 정보이고 192.168.0.1이 현재의 gateway이다.

root@localhost:/etc/network# ip route
default via 192.168.0.1 dev eth0  metric 100
169.254.0.0/16 dev eth0  scope link  metric 1000
192.168.0.0/24 dev eth0  proto kernel  scope link  src 192.168.0.88

위의 정보만으로 네임서버가 안된다면 다음 정보를 추가하도록 하자.
네임서버란 www.google.com, www.naver.com과 같이 숫자로 된 IP가 아닌 사람이 쉽게 파악할수 있는 형태의 IP 지정 방식이다.
아래의 경로에 tail이라는 파일을 생성해서(만일 없다면 새로 생성한다) nameserver 8.8.8.8을 입력하고 파일을 저장한다.

vi /etc/resolvconf/resolv.conf.d/tail
nameserver 8.8.8.8

이상으로 설정을 완료되었고 네트워크 장비를 재시작 한다.

#  ip addr flush enp0s3 && systemctl restart networking.service

만일 구버전 리눅스라면 위의 명령이 실행되지 않을수 있다. 그럴 경우는 아래와 같이 하자.

ip addr flush enp0s3 && /etc/init.d/networking restart


여기까지가 Ubuntu 시스템 상에서의 고정 IP 설정하는 법이다.
정상적으로 설정이 되었다면 터미널 창에서 ping이 정상적으로 송수신될 것이다.

# ping www.google.com

위 명령이 정상적으로 수행된다면 IP 설정과 nameserver 설정이 제대로 된 것이다.

그런데 ping 172.217.24.4과 같이 IP로는 실행되나 도메인 네임으로는 수행되지 않는다면 이건 nameserver 설정이 잘못되었다는 뜻이다.

2017년 12월 18일 월요일

C#의 델리게이트(delegate)에 대한 기본적인 개념 및 간단한 예제





delegate는 C#이 가진 독특한 개념인데 따라서 기본적으로 생소한 개념으로 다가온다.
아래는 delegate에 대한 기본적인 개념 소개와 간단한 예제를 통해 개념을 이해해 보고자 한다.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EXdelegate1
{
    class Program
    {
        //delegate란 
        // (1) 일종의 데이터 type과 같다. 
        // (2) 어떤 메소드를 encapsulate할수 있다. 즉 어떤 메소드를 가리킬수 있고
        //      이를 통해 그 메소드를 실행할수 있다.
        // (3) similar to function pointer in C, C++
        // (C, C++에서 어떤 함수를 포인터로 가리키고 이 포인터를 이용해서 
        // 해당 함수를 실행하는 방식)
        //아래는 새로운 데이터 타입과 같은 delegate를 선언하는데 이 새로운 데이터 타입의
        //이름은 Del이고
        //이 데이터 타입이 가리킬수 있는 메소드는 argument로 string을 하나 받고
        //반환 값은 void인
        //메소드가 있다면 그들 메소드들은 모두 delegate 이름이 Del인
        //새로운 데이터 타입으로 객체를 선언하고
        //이 선언된 delegate 타입의 객체로 그들 메소드들을 실행할수 있다.
        private delegate void Del(string message);

        //아래는 새로운 데이터 타입인 MyDel이라는 이름의 데이터 타입을 선언하는데
        //이 데이터 타입은
        //특별히 delegate형태의 데이터 타입이다.
        //MyDel라는 데이터 타입으로 선언된 어떤 객체(참조변수)는 argument로 string 타입 하나,
        //int 타입 하나를
        //받고 반환 데이터는 string 타입을 반환하는 모든 메소드를 MyDel이라는
        //데이터 타입으로 모두 
        //가리키고 또 실행할수 있다는 개념이다.
        private delegate string MyDel(string info, int some);

        static void Main(string[] args)
        {
            //delegate 객체를 생성. 이 delegate 객체가 실행할 메소드는 DelegateMethod()이다.
            //handler는 delegate Del 타입의 object이다
            //delegate 타입의 객체 생성은 Java나 기존의 객체 생성 방식과 약간 다르다.
            //Del이라는 delegate 타입의 객체 handler가 실행할 메소드 이름을 대입해 주면 
            //delegate Del 타입의 새로운 객체가 하나 생성된다.
            Del handler = DelegateMethod;
            handler("delegate object가 보낸 메시지");

            MyDel another = AnotherDelegateMethod;

            Console.WriteLine("\n\n 이름 : " + another("홍길동", 25));

            Del myDel = mDel;
            myDel("Del이라는 이름의 delegate가 실행하는 두~~ 번째 메소드임");

            //익명함수 방식으로 delegate 객체 생성
            //익명함수란 원래 객체 생성 시점에 함수(메소드)의 이름은 없이 막바로 함수 본체가
            //주어지면서 객체 생성되는 형태이다.
            //따라서 아래에서 MyDel의 조건인 매개인자로 string 하나, int형 하나를 받고
            //반환 데이터 타입이 string이라는 조건만 만족시켜주면 함수 본체는 
            //내용이 어떠하든지 상관이 없다.
            MyDel anonymMyDel = delegate (string addr, int sex)
            {
                string gender = "";

                if (sex == 1)
                {
                    gender = "남성";
                } else if (sex == 0)
                {
                    gender = "여성";
                }
                return "주소 : " + addr + ", 성별 : " + gender;
            };

            Console.WriteLine(anonymMyDel("서울특별시 강남구 아무개길 77", 1));


            //익명함수 방식의 delegate 객체 생성2
            Del anonymDel = delegate (string msg)
            {
                Console.WriteLine("\n\nDel의 익명함수 방식 객체 : "+msg);
            };

            anonymDel("오 이런 식으로 되는구나\n\n\n");
        }


        private static void DelegateMethod(string msg)
        {
            Console.WriteLine("\n여기는 delegate method\nmessage sent from delegate object : \""+msg+"\"");
        }

        private static string AnotherDelegateMethod(string name, int age)
        {
            return name + ", age : " + age;
        }

        private static void mDel(string info)
        {
            Console.WriteLine("\n\n여기는 " + info + "~~~\n\n");
        }

    }
}

2017년 12월 14일 목요일

리눅스 Shell command를 이용한 Socket 통신하기





리눅스 Shell command를 이용한 Socket 통신하기

리눅스의 cat 명령어는 쓰임새가 다양하다. 해보진 않았지만 cat을 이용해서 리눅스 시스템을 백업도 가능한듯 하다.
리눅스에 있는 kkk.txt라는 파일의 내용을 Socket을 이용해서 내보낼수 있는가?
netcat(혹은 줄여서 nc)와의 조합을 통해서 가능하다.
아래와 같이

cat kkk.txt | nc 192.168.0.7 8801

여기서 192.168.0.7은 서버 소켓의 IP 주소이고, 8801은 서버 소켓의 포트번호이다.
혹은 간단한 문자열 정도를 보낼려면 이렇게도 가능하다.

echo "This string is from Linux" | netcat 192.168.0.7 8801

통신상태 확인을 위해 간단히 사용할수 있을 것이다.

2017년 12월 13일 수요일

python에서의 문자열 비교





파이썬(python)에서의 문자열 비교에 대한 간단한 예제 코드이다.

#-*- coding: utf-8 -*-

from operator import eq  # 문자열 비교를 위한 함수 eq 추가

chk = "joe7"

if chk == "joe":
    print "같다."
else:
    print "다르다."

위의 코드는 C style 언어들의 보편적인 비교 법이다. 
그런데 Java에서는 저런식으로 비교가 안된다는 약간이 당황스러움.
말이 나온김에 Java의 문자열 비교를 보자면
if (chk.equals("joe")) { ... }와 같이 해야 한다.

위 코드 실행 결과는 당연히 "다르다"가 출력될 것이다.
이번에는 문자열 비교 함수 eq()를 이용한 방식이다.

아래 코드에서 eq(chk, "joe")는 괄호 안의 2개의 매개인자가 같으면 true, 아니면 false가 된다.
결과는 당연히 "다름"이 출력된다.

if eq(chk, "joe"): 
    print "같음"
else:
    print "다름"

다음으로 문자열 비교에서 매우 유용하게 사용되는 특정 문자를 "포함하고 있는지"에 대한 파이썬에서의 용법이다.
find()라는 메소드를 활용하면 된다. find()메소의 syntax는 다음과 같다.

str.find(s, beg=0, end=len(str))

str이라는 문자열에서 문자열 s가 발견 되어지면 발견되어진 위치 값(index값)을 반환한다.
만일 발견되지 않으면 -1을 반환한다. 이때 문자열의 index는 0부터 시작한다.

beg : 문자열 str에서 s라는 문자열을 찾을 때 시작 Index 값이다. 지정하지 않으면 default로 0가 지정된다.

end : 문자열 str에서 s라는 문자열을 찾을 때 종료될 index 값이다. 지정하지 않으면 문자열 str의 length 값이 지정된다.

예를들어

str = "<EOF>"가 있다면
index 0 : <
index 1 : E
index 2 : O
index 3 : F
index 4 : >
가 된다.

print str.find("EOF")를 하면 1이 반환된다.
print str.find("<EOF>")를 하면 0이 반환된다.
print str.find("efgh")를 하면 -1이 반환된다.
print str.find("OF")를 하면 2가 반환된다.
print str.find("F")를 하면 3이 반환된다.
print str.find("OF>")를 하면 2가 반환된다. OF>라는 전체 문자열이 str에서 발견되기 때문이다.
print str.find("OF)")를 하면 -1이 반환된다. OF까지는 문자가 포함되어 있지만 OF)라는 문자는 포함되어 있지 않기 때문이다.

info = "Hello world<EOF>"
print info.find(str)를 하면 11이 반환된다.

print info.find(str, 5)를 하면 str을 찾는 위치를 5위치에서 찾지만 반환은 전체 위치에서의 index값을 반환하므로 11이 반환된다.

print info.find(str, 5, 7)를 하면 str을 info에서 찾을 때 시작 위치를 5에서 시작해서 7위치까지만 찾으므로 str이 발견되지 않는다. 따라서 -1이 반환된다.

아무튼 python에서 java의 contains()와 같은 기능을 find()를 이용해서 할수 있다는 얘기다.
java의 contains()와는 약간 개념이 다르지만 -1이 아니면 해당 문자열을 포함하고 있는 것이 되는 것이다. 예를 들어 data라는 변수에 소켓통신으로부터 데이터를 읽었을 경우 data "EOF"라는 문자열이 포함되어 있다면 소켓통신의 읽기 작업을 종료하는 코드를 작성할려면 다음과 같이 하면된다.

if data.find("EOF") != -1 :
    print "OK, Job completed"
    break;

과 같이하면 될 것이다.

2017년 12월 7일 목요일

C#의 .dll Library를 C++에서 활용하기





C#의 .dll Library를 C++에서 활용하기
C#에서 생성한 .dll파일과 .tlb 파일을 C++의 해당 프로젝트 폴더안과 .exe 실행파일이 있는 곳에 복사해 넣는다.
이제 남은 것은 C++ 소스코드 작성법만 알면 C#에서 만든 .dll Library를 C++에서 사용할수 있게 된다.
샘플 예제 코드는 다음과 같다. 각 부분에 대한 설명은 아래 코드상에 있다.
아래 코드는 MFC에서 정보 확인 버튼 클릭시 C# .dll Library의 특정 메소드(함수)를 실행하고 C# .dll이 던져주는 정보(결과)를 보여주는 기능을 구현한 코드 조각이다.

이때 다음과 같이 .tlb 파일을 #include 아랫쪽에 import해 주어야 정상적으로 C# .dll 라이브러리에 있는 메소드들을 MFC에서 사용할수 있다.

#import "ExMakeClassLibSerialRead.tlb" no_namespace named_guids


//MFC에서 C#의 라이브러리 .dll을 이용한다.
void CMFC_UseCsharpDllDlg::OnBnClickedButton1()
{
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

//ICallClass는 C#의 interface이름이다.
//public interface ICallClass { .... }과 같이 C#에서 선언되어 있는 부분이다.
ICallClass *csharp = NULL;

//CoInitialize()
//==> Initializes the COM library on the current thread and identifies 
     //        the concurrency model 
//==> as single-thread apartment (STA).
//COM이란, 컴포넌트 오브젝트 모델(Component Object Model, COM)은 
//마이크로소프트가 개발한 소프트웨어 구성 요소들의 응용 프로그램 이진 인터페이스이다. 
//COM을 이용해 개발된 프로그램들은 프로세스간 통신과 동적 오브젝트 생성이 가능하다. 
CoInitialize(NULL);

//typedef long HRESULT
//CoCreateInstance(CLSID_C#의클래스이름, NULL, CLSCTX_INPROC_SERVER, 
//                                IID_C#의interface이름, 
     //                                reinterpret_cast<void**>
     //                                (&C#의interface이름type으로선언된참조변수명));
//아래의 CLSID_Class1에서 Class1은 C#에서 사용자가 필요로하는 기능을 담고 있는 
     //클래스인데 C#에서 다음과 같이 정의되어 있다.
//public class Class1 : ICallClass { ... } 자세한 것은 C#의 ExMakeClassLibSerialRead
     //라는 이름의 프로젝트를 참조할 것
//아래에서 IID_ICallClass에서 ICallClass는 C#에서 public interface ICallClass { ... }
     //와 같이 선언된 부분의 interface 이름이다.
HRESULT hr = CoCreateInstance(CLSID_Class1, NULL, CLSCTX_INPROC_SERVER, IID_ICallClass, reinterpret_cast<void**>(&csharp));
//SUCCEEDED macro
//Provides a generic test for success on any status value.
//BOOL SUCCEEDED(HRESULT hr);
//==> hr : The status code.This value can be an HRESULT or an SCODE.
     //               A non - negative number indicates success.
//==> Return value : TRUE if hr represents a success status value; 
     //                  otherwise, FALSE.
if (SUCCEEDED(hr))
{
//showInfo() 함수는 C#에서 public void showInfo() { ... }과 같이 
          //정의되어 있는 C#용 메소드이다.
csharp->showInfo();

CString msg = csharp->getInfo();
MessageBox(msg);

} else {
MessageBox(_T("실패~\n\nC++의 exe 실행파일이 있는 위치에 \nC#에서 생성한 .dll과 .tlb가 있어야 합니다."));
}
}

C# 프로그램을 .dll Library로 만들기(C++에서 사용하기 위해)





C#에서 .dll로 library를 만들고 이것을 C++에서 사용하기
C#은 MFC에 비해 여러면에서 개발자 편의적이다. 따라서 C#으로 만든 프로그램을 .dll 라이브러리로 만들고 이것을 C++ 혹은 MFC에서 사용하는 방법에 대해서 다루고자 한다.

전개순서는

-. C#을 클래스 라이브러리로 개발하기
-. C#을 .dll library로 만들기 위한 Visual Studio 2017에서 속성 설정하기
-. C# 프로그램이 .dll library로 MFC(혹은 C++)와 동작하기 위한 소스 코드 작성법
-. C# 소스코드 빌드하기
-. 빌드된 C#의 .dll을 레지스트리 등록 및 .tlb 파일 만들기
-. C++ 쪽에서 C#용 .dll 파일 사용하기 위한 소스 코드 작성법

의 순서로 진행이 된다.

1) C#을 클래스 라이브러리로 개발하기

Visual Studio 2017 파일 - 새로 만들기 - 프로젝트 - 클래스 라이브러리(.NET Framework)


이렇게해서 소스코드 창이 열리면 

2) C#을 .dll library로 만들기 위한 Visual Studio 2017에서 속성 설정하기
다음과 같이 해당 프로젝트의 속성을 C# 클래스 라이브러리(.dll) 용으로 설정해 준다.





3) C# 프로그램이 .dll library로 MFC(혹은 C++)와 동작하기 위한 소스 코드 작성법
소스 코드는 크게 두 부분으로 구성이 된다. C++과 통신하기 위한 interface 하나와 필요한 사용자 기능을 위한 class 하나이다. 이 둘에 대한 Guid 값을 지정해 주어야 하는데 Guid 값을 얻기 위해서는 아래 그림과 같이 하면된다.



'복사' 버튼을 클릭하여 Guid 값을 소스코드에(interface와 사용자 클래스에) 붙여넣으면 된다.
소스 코드는 아래와 같다. Guid가 붙여진 곳을 확인해보자.
여기서 또 기억할 사항은 Guid값을 소스코드에서 사용할수 있기 위해서는 using System.Runtime.InteropServices를 추가해 주어야 한다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace ExMakeClassLib
{
    //여기가 C++과 연동하기 위한 interface부분이다.
    [Guid("357CCEDB-44A1-481E-A42A-0D4DCA8C5EEA")]
    public interface ICallClass
    {
        //여기에 C++에서 사용할 메소드들의 프로토타입을 지정해 주면 된다.
        //C++에서 사용하게 될 public 형태의 메소드들이 여기에 해당된다.
        //private 모드의 메소드는 여기에 지정이 불가능하다. 
        //생성자는 여기서 지정해 줄 필요가 없다.
        //메소드들만 지정해 주면 된다.
        void setInfo(String _name, int age, String _phoneNum);
        void showInfo();
    }


    //여기가 사용자가 원하는 기능을 구현한 클래스이다.
    [Guid("D00C0769-26E7-4B4E-A7D0-8CAAE2AB3702")]
    public class Class2 : ICallClass
    {
        private String name;
        private int age;
        private String phoneNum;
        //여기서 주의 해야할 사항은 SerialPort나 Thread 같은경우 처음 사용 후에 
        //포트를 닫는 동작이 필요하거나 Thread를 종료시키는 동작이 필요할 경우 등
        //여러 메소드들에서 사용을 해야하는 멤버 변수들은 반드시 static으로
        //선언해 주어야 한다. 왜냐하면 비록 전역 변수 형태로 선언되었다 할지라도
        //처음 사용했던 메소드가 아닌 다른 메소드에서 또 사용되어질 경우는
        //해당 멤버 변수(Thread, SerialPort...)가 null 상태가 되어 버린다.
        //따라서 C++에서 계속해서 해당 멤버 변수를사용할수 있도록 하기 위해서는
        //반드시 static으로 선언해 주어야 한다.

        public Class2() { }

        public Class2(String _name, int _age, String _phoneNum)
        {
            name = _name;
            age = _age;
            phoneNum = _phoneNum;
        }

        public void showInfo()
        {
            Console.WriteLine("▶ name : " + name + "\n▶ age : " + age + "\n▶ 폰번호 : " + phoneNum);
            Console.WriteLine("---- This is C# Library for C++ from Joe");
        }

        public void setInfo(String _name, int _age, String _phoneNum)
        {
            name = _name;
            age = _age;
            phoneNum = _phoneNum;
        }
    }
}


4) C# 소스코드 빌드하기
Visual Studio 2017의 메뉴에서 '빌드' - '해당프로젝트명 빌드'를 클릭하여 소스 코드를 빌드한다.
(아래 이미지의 경우는 프로젝트 명이 ExMakeClassLibSerialRead라고 가정할 경우이다)


C#의 프로젝트명\bin\Debug\해당프로젝트명.dll 이 생성이 되어 있을 것이다.


5) 빌드된 C#의 .dll을 레지스트리 등록 및 .tlb 파일 만들기
.tlb 파일을 만드는 이유는 C#은 .NET 기반이기 때문에 이를 C++에서 사용가능하도록 하기 위해서이다.

"Developer Command Prompt for VS 2017"창을 관리자 권한으로 열고 C#의 해당 .dll이 있는 경로로 이동한다.
"Developer Command Prompt for VS 2017"창을 열어야 regasm을 정상적으로 이용할수 있다.
여는 방법은 윈도우즈의 시작 - Visual Studio 2017 하위 항목 열기 - Developer Command Prompt for VS 2017 메뉴 위에서 마우스 우측 클릭 - 관리자 권한으로 실행한다.
(만일 DOS 창을 관리자 권한으로 열어서 사용할 경우는 regasm.exe를 C#의 .dll이 있는 폴더로 복사해서 아래 명령을 처리하면된다)

C#용 .dll이 있는 디렉토리로 이동해서 다음 명령을 한다.
아래에서 ExMakeClassLib는 프로젝트 명이다.

regasm ExMakeClassLib.dll /tlb:ExMakeClassLib.tlb

그러면 다음과 같은 메시지가 보이면 성공한 것이다.

Microsoft .NET Framework 버전 4.7.2556.0용
Microsoft .NET Framework Assembly Registration Utility 버전 4.7.2556.0
Copyright (C) Microsoft Corporation.  All rights reserved.

형식이 등록되었습니다.
어셈블리를 'D:\Joe\CSharp\ExMakeClassLib\ExMakeClassLib\bin\Debug\ExMakeClassLib.tlb'(으)로 내보내고 형식 라이브러리를 등록했습니다.

.dll과 .tlb 생성은 C# 소스 코드를 수정할 때마다 항상 같이 만들어 주어야 한다.
이렇게 만들어진 .dll과 .tlb를 C++의 프로젝트가 있는 디렉토리와 C++의 실행파일이 있는 위치에 복사해 준다.
이렇게 만들어진 C#용 library를 C++(혹은 MFC)에서 어떻게 사용하는지는 아래 링크에서 확인할수 있다.
(C++에서 C# .dll 라이브러리 사용하는 법)



















2017년 12월 5일 화요일

Visual Studio 2017에서 작업을 완료할수 없습니다. 해당 인터페이스를 지원하지 않습니다





Visual Studio 2017에서 C#으로 프로그래밍 중 "참조" - "참조 추가" 작업시 아래와 같은 에러 발생했을 경우에 대한 해법이다.

"작업을 완료할수 없습니다. 해당 인터페이스를 지원하지 않습니다."
(The operation could not be completed, no such interface supported)


아래 경로로 이동한다.
cd C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies

원하는 것은 Microsoft.VisualStudio.Shell.Interop.11.0.dll 파일이 있는 곳으로 이동해야 한다.

다음 명령을 실행한다.

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies>gacutil -i Microsoft.VisualStudio.Shell.Interop.11.0.dll

그러면 다음과 같은 결과가 나타나면 문제가 해결 된 것이다. 

『  Microsoft (R) .NET Global Assembly Cache Utility.  Version 3.5.30729.1
    Copyright (c) Microsoft Corporation.  All rights reserved.

   Assembly successfully added to the cache  』

Visual Studio 2017을 다시 실행한다.

그런데 gacutil.exe가 해당 경로에는 없다. 그런데 gacutil.exe가 여러 군데, 여러 버전이 있는 것 같다. 파일 사이즈가 상이한 동일 이름의 gacutil.exe가 여러 군데 있다. 내 경우는 아래 경로에 있는 gacutil.exe로 문제 해결되었다.

C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\gacutil.exe

자세한 내용은 아래 사이트 참조
https://developercommunity.visualstudio.com/content/problem/75105/cant-add-reference-error-pops-up-no-such-interface.html

2017년 12월 4일 월요일

MFC에서 문자열 합치기





Java나 C#을 사용하다가 MFC를 사용하면서 느끼는 짜증나는 불편함이 있다면 문자열 처리에 대한 것이다.
Java나 C#에서의 문자열 처리의 개념으로 MFC에서 시도해보면 당황 스러울 정도로 답답함이 느껴진다.
본 포스트는 MFC에서의 문자열을 사용할수 있는 나름 편리한 도구인 CString을 이용한 코드조각을 소개하고자 한다.

같은 CString끼리는 + 연산으로 쉽게 두 문자열을 합칠수 있으므로 여기서는 CString 타입과 UINT 타입을 합쳐서 하나의 문자열로 만드는 경우를 다뤄보고자 한다.

현재 사용가능한 Serial Port를 출력한다고 가정할 경우 COM이라는 문자열과 1,2,3...과 같은 숫자를 하나의 문자열로 합친다면 다음과 같이 처리하면 된다.

#include <iostream>
using namespace std;

CString str = "COM";
CString port;
//UINT k = 3;
//str += k; //불가능

for (UINT i = 0; i < 7; i++)
{
     //CString과 UINT가 막바로 port = str + i와 같이 합쳐지지 않으므로 
     //UINT를 CString형으로 변환하는 작업
port.Format("%d", i);
port = str + port;

cout << "Available Serial Ports : " << port << endl;
printf("Ports : %s\n", port);
}

그런데 여기서 또 답답한 건 이 소스 코드의 문자 집합이 유니코드 집합을 사용할 경우는 컴파일 단계에서 아예 에러를 뿜는다.

CString str = "COM"; 이 코드에서는 『"const char [4]"에서 "ATL::CStringT<wchar_t, StrTraitMFC_DLL<wchar_t, ATL::ChTraitsCRT<wchar_t>>>"(으)로 변환하기 위한 적절한 생성자가 없습니다라』는 에러를 뿜는다.

port.Format("%d", i); 이 코드에서는 
『인수 목록이 일치하는 오버로딩된 함수 ...의 인스턴스가 없습니다.
              인수 형식이(const char [3], UINT)입니다....』와 같은 에러가 발생한다.

이건 현재 Visual Studio에서 사용중인 문자 집합을 "멀티바이트 문자 집합 사용"으로 변경해 주어야 한다.
문자셋 인코딩 문제는 항상 모든 프로그래밍 언어들에서 신경쓰야 할 부분이지만 이건 정상적인 문법 자체가 잘못됐다고 에러를 내 보내니... 답답한 노릇이다.

문자 집합을 변경하는 방법은
해당 프로젝트 이름에 마우스 우측 클릭 ⇒ 팝업 메뉴에서 "속성" 클릭 ⇒ 해당 프로젝트의 속성 페이지 창에서 ⇒ 구성 속성의 하위 항목 중 "일반" ⇒ 우측 항목들 중 "문자 집합"의 드랍 다운 메뉴에서 "멀티바이트 문자 집합 사용"을 선택 ⇒ 확인


위의 코드를 실행하면 다음과 같을 결과를 내 보이게 될 것이다.

Available Serial Ports : COM0
Ports : COM0
Available Serial Ports : COM1
Ports : COM1
Available Serial Ports : COM2
Ports : COM2
Available Serial Ports : COM3
Ports : COM3
Available Serial Ports : COM4
Ports : COM4
Available Serial Ports : COM5
Ports : COM5
Available Serial Ports : COM6
Ports : COM6


2017년 11월 23일 목요일

python에서 Linux shell command 사용하기





파이썬에서 Linux의 shell command를 사용하는 방법이다.
이를 위해서는 파이썬의 os모듈과 sys모듈을 import해야 한다.
다음은 코드 조각이다.

import os
import sys

os.system('cat qqq.txt > /dev/ttyPS0')

위의 소스코드는 현재 디렉토리에 있는 qqq.txt라는 파일의 내용을 ttyPS0라는 장치(리눅스에서는 장치도 file로 취급)로 전송하는 기능을 수행한다.
여기서 ttyPS0는 현재 디바이스가 가지고 있는 Serial port이다. 만일 현재 디바이스가 임베디드 장비이고 이 장비의 USB UART에 USB 케이블로 PC와 연결되어 있다면 위의 코드를 실행하고 PC쪽에서 Serial 포트의 데이터를 읽는 프로그램을 만들면 임베디드 장비의 qqq.txt의 내용을 PC에서 수신할수 있게 될것이다.

혹은 반대로 하면

os.system('cat /dev/ttyPS0')

이상과 같이 하면 현재의 장비의 Serial port로 들어오는 데이터를 터미널 창에 표시해주는 리눅스의 shell command를 python에서 사용하는 방법이었다. 이 경우는 PC에서 Serial port로 전송하는 데이터를 임베디드 장비에서 수신할수 있는 쉘 명령어를 python에서 사용한 방식이 되겠다.

만일 shell command의 내용을 변수에 저장하고자 한다면 subprocess라는 것을 import해 주어야 한다.

import subprocess

aaa = subprocess.check_output(['cat', 'kkk.txt'])
print aaa

와 같이 하면 kkk.txt의 내용을 변수 aaa에 저장했다가 이것을 출력하는 코드가 되겠다.

혹은 

mylist = subprocess.check_output(['ls', '-l'])
print mylist

이렇게하면 현재 디렉토리에 있는 파일 목록을 변수 mylist에 담고 그것을 출력하는 기능을 수행하는 코드이다.

혹은 보다 간단한 방법은 shell 옵션을 True로 하면 터미널 창에서 입력하는 형태 그대로의 command를 사용할수 있다.

pid = subprocess.check_output('ps -elf | grep CRSViewer.py', shell=True)


이렇게 하면 CRSViewer.py의 실행 process id를 나타내 준다.


2017년 11월 22일 수요일

C# 초 간단 쓰레드(Thread) 예제





"Start Thread" 버튼 클릭시 0.5초 간격으로 1부터 순차적으로 숫자를 출력하고 "Stop Thread" 버튼 클릭시 쓰레드를 종료하는 초 간단 C#용 Thread 예제 코드이다.
화면 구성은 다음과 같다.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ExSerialRead
{
    public partial class Form1 : Form
    {
        private int cnt = 0;
        private Thread rTh;

        public Form1()
        {
            InitializeComponent();
        }

        private void btnStartThread_Click(object sender, EventArgs e)
        {
            //Thread 객체 생성. 생성시 이 Thread가 실행할 메소드를 지정해 준다. 
            //여기서는 Count라는 메소드를 rTh라는 쓰레드가 실행하게 될 것이다.
            rTh = new Thread(Count);
            rTh.Start();
        }

        // Thread로 수행할 작업
        private void Count()
        {
            while(true)
            {
                ++cnt;
                Thread.Sleep(500);
                //Console.WriteLine("crr cnt : " + cnt); 
                Console.WriteLine("crr cnt is {0} ", cnt); 

                if (cnt > 200)
                {
                    rTh.Abort(); //쓰레드 강제 종료

                    Console.WriteLine("Thread 종료. cnt : " + cnt);
                    break;
                }
            }
        }

        private void btnStopThread_Click(object sender, EventArgs e)
        {
            rTh.Abort(); //쓰레드 강제 종료
            Console.WriteLine("Thread 종료~");
        }
    }
}



2017년 11월 13일 월요일

dd command를 이용한 리눅스 시스템 전체를(디스크 전체 혹은 파티션 전체를) 부팅 가능 USB로 복사하기





dd command를 이용한 리눅스 시스템 전체를(디스크 전체 혹은 파티션 전체를) 부팅 가능 USB로 복사하기
(Ubuntu를 중심으로)

리눅스를 사용하다보면 현재의 시스템 설정상태, 현재 설치되어있는 각종 패키지들의 그 상태 그대로 시스템 전체를 백업 받을 일이 종종 발생한다.

시스템 해킹에 대한 복구 차원에서도 현재의 시스템 그대로를 복제해 두는 일은 필요하다.
또한 리눅스를 탑재한 임베디드 시스템의 경우 해당 시스템이 가동되는데 필요한 패키지들이 다 설치된 상태 그대로 제품 양산시 SD card에 그대로 시스템을 복제해야 할 경우등에도 꼭 필요한 작업이 디스크 전체(현재의 리눅스 시스템 전체)를 복제하는 일이다.

특별히 임베비디 장비의 경우 리눅스가 최신 버전이 설치되기 보다는 옛날 버전을 설치하는 경우가 흔한데 이렇게 구 버전의 리눅스 시스템의 경우 필요한 패키지들을 설치하는 작업이 여간 번거러운 작업이 아니다.
게다가 시스템 architecture가 흔히 쓰는 경우가 아닐경우는 정람 패키지 설치 작업이 난감한 경우들이 있다.
이럴 경우 설치 패키지들을 다운 받아서 off-line 상태에서 dpkg를 이용해 설치하는 궁여지책을 쓰는 경우도 있지만 그러나 이것도 제품 양산을 염두에 둔다면 그렇게 쓸모가 좋은 모양새는 아니다.
그래서 현재의 리눅스 시스템 전체(디스크 전체)를 복제하는 것이 매유 요긴해 진다.
그런데 이런 기능을 제공해주는 툴들이 리눅스는 매우 다양하다.
remastersys, genisoimage, partimage, gnome-disks, clonezilla... 
그런데 이런 툴들도 구버전 리눅스 시스템에서는 아예 설치가 안되는 경우도 있고 어떤 건 구버전에서만 기능이 동작하는 경우도 있는 등 쉽지 않는 작업이다.
리눅스 자체가 한 회사에서 만든 Windows와 달리 수 많은 사람들에 의해서 만들어지다 보니 그럴수 밖에 없다.

아무튼 구버전, 신버전 등 가리지 않고 위의 목적을 달성할수 있는 아주 좋은 command가 다름아닌 dd이다.
명령어 같지 않게 생긴 강력한 명령어가 dd이다.
dd는 원래는 Data Duplicator를 뜻하는 명령어인데 이걸 잘못 사용하면 디스크 자체를 통체로 날려먹는 경우들이 있어 Disk Destroyer라는 별명이 붙을 있을 정도이다.

dd command는 리눅스 시스템 차원에서의 command이기 때문에 별도의 패키지를 설치할 필요가 없다.
dd가 할수 있는 기능은 다양하다. 그러나 본 포스트에서는 아래의 미션을 위한 것으로 한정하고자 한다.

-. 임베디드 시스템의 현재의 설치된 패키지, 현재의 설정된 시스템 상태 그대로를 USB 메모리에 복제를 한다.

-. 복제된 USB 메모리는 부팅이 가능해야 한다.

-. 복제된 USB 메모리를 동일한 임베디드 보드에 꽂으면 해당 임베디드 보드도 부팅시 현재의 리눅스 시스템과 모든 조건이 동일한 시스템으로 동작이 되어야 한다.

작업을 위해 필요한 조건 혹은 필요한 상태

-. 임베디드 시스템에 USB 외부 메모리가 mount되어 있다. 이곳에 시스템 복제본이 저장될 공간이다.
   (외부 USB 메모리 마운트는 Disk Utility라는 기본 설치된 프로그램을 실행해서 해도 된다. 혹은 터미널 창에서 마운트를 해도 된다)

dd command 기본 사용법

# dd if=복제할_대상이_되는_디스크_장치_이름 of=복제본을_저장할_마운트된_USB_장치의_이름 bs=복사속도

위의 옵션들에 들어갈 정보는 fdisk -l 명령어를 통해서 확인할수 있다. 이 명령어의 결과는 다음과 같은 정보들이 표시된다.

root@joe-VirtualBox:/home/joe/다운로드# fdisk -l
Disk /dev/sda: 30.2 GiB, 32377438208 bytes, 63237184 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xe46ece9d

Device     Boot    Start      End  Sectors  Size Id Type
/dev/sda1  *        2048 54849535 54847488 26.2G 83 Linux
/dev/sda2       54851582 63236095  8384514    4G  5 Extended
/dev/sda5       54851584 63236095  8384512    4G 82 Linux swap / Solaris


Disk /dev/sdb: 3.8 GiB, 4009754624 bytes, 7831552 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x04831162

Device     Boot  Start     End Sectors  Size Id Type
/dev/sdb1  *        63  262207  262145  128M  c W95 FAT32 (LBA)
/dev/sdb2       262208 6291455 6029248  2.9G 83 Linux

위 정보를 보면 2개의 디스크가 현재 리눅스 시스템에 마운트 되어 있음을 알수 있다.
여기서 주목해야할 정보는 
첫 번째 디스크의 장치명과 용량은 
Disk /dev/sda: 30.2 GiB ...
-. 장치명 : /dev/sda
-. 용량 : 30.2 Gb
-. partition 정보 : sda1, sda2, sda5

두 번째 디스크의 장치명과 용량은 
Disk /dev/sdb: 3.8 GiB
-. 장치명 : /dev/sdb
-. 용량 : 3.8 Gb
-. partition 정보 : sdb1, sdb2

아무튼 위의 정보들로부터 dd에서 사용하게 될 정보들이 다 나와있다.
dd의 if는 Input File의 약자이고 of는 Output File의 약자이다.
우리는 현재의 리눅스 시스템이 설치되어 있는 디스크인 /dev/sda에 있는 모든 것들을(부팅 영역까지) /dev/sdb에 복제를 하고자 한다.

# dd if=/dev/sda of=/dev/sdb bs=64K

이렇게 하면 USB 메모리에 완전한 형태로의 시스템이 그대로 복제되었다.