python openCV로 외부 IP cam 영상 제어(onvif)

프로그래밍 언어 2022. 11. 16. 15:28

이번에 일정 시간에 한 번씩 외부에 있는 영상을 사진으로 저장해야 하는 일이 생겼다.

 

다른 건 비용이 비싸서 최대한 저렴하게 알아본 결과

IP Cam + LTE모뎀으로 조합했다.

IP Cam은 7만원대, LTE모뎀은 16만원대?였나.. 요금제는 고정IP로 매월 3만원대....

더 싸게 할 수도 있긴하던데... 과제비 꿀꺽ㅋ

(카메라 더 비싼걸 살 걸 그랬다... 발열이 있네?;;)

 

 

 

우선 카메라 설정부터 하여 'ispy'라는 프로그램으로 PC에서 접속 확인을 했다.

아래는 셋팅 및 영상 확인 순서.

ID와 비번, 지금은 로컬 네트워크라 192대역으로 IP 입력함
이렇게 되면 완료

 

 

 

영상은 파이썬, openCV로 제어했다.

 

openCV는 알다시피

> pip install opencv-python

 

코드는 복붙이 정석이지..ㅋㅋㅋㅋ

import cv2
import datetime
import os


def writeVideo():
    #현재시간 가져오기
    currentTime = datetime.datetime.now()
   
    #RTSP를 불러오는 곳
    video_capture = cv2.VideoCapture('rtsp://userID:password12@192.168.100.98:554/stream1')
   
    # 웹캠 설정
    video_capture.set(3, 1280)  # 영상 가로길이 설정
    video_capture.set(4, 720)  # 영상 세로길이 설정
    fps = 12
    # 가로 길이 가져오기
    streaming_window_width = int(video_capture.get(3))
    # 세로 길이 가져오기
    streaming_window_height = int(video_capture.get(4))  
   
    #현재 시간을 '년도 달 일 시간 분 초'로 가져와서 문자열로 생성
    fileName = str(currentTime.strftime('%Y %m %d %H %M %S'))

    #파일 저장하기 위한 변수 선언
    path = f'C:\\Users\\USER\\Desktop\\Projects\\05 Sewage Level Meter\\SourceCode\\python\\Cam\\sourceCode\\{fileName}.avi'
   
    # DIVX 코덱 적용 # 코덱 종류 # DIVX, XVID, MJPG, X264, WMV1, WMV2
    # 무료 라이선스의 이점이 있는 XVID를 사용
    fourcc = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
   
    # 비디오 저장
    # cv2.VideoWriter(저장 위치, 코덱, 프레임, (가로, 세로))
    out = cv2.VideoWriter(path, fourcc, fps, (streaming_window_width, streaming_window_height))

    while True:
        ret, frame = video_capture.read()
        # 촬영되는 영상보여준다. 프로그램 상태바 이름은 'streaming video' 로 뜬다.
        cv2.imshow('streaming video', frame)
       
        # 영상을 저장한다.
        out.write(frame)
       
        # 1ms뒤에 뒤에 코드 실행해준다.
        k = cv2.waitKey(1) & 0xff
        #키보드 esc 누르면 종료된다.
        if k == 27:
            cv2.imwrite(f'C:\\Users\\USER\\Desktop\\Projects\\05 Sewage Level Meter\\SourceCode\\python\\Cam\\sourceCode\\shot.png', frame)
            break
    video_capture.release()  # cap 객체 해제
    out.release()  # out 객체 해제
    cv2.destroyAllWindows()

if __name__ == "__main__":
    writeVideo()

 

https://pcseob.tistory.com/25 (소스 참고. 감사합니다ㅋㅋ)

 

 

소스에서

video_capture = cv2.VideoCapture('rtsp://userID:password123@192.168.100.98:554/stream1')

이 부분의 주소를 ispy 사진 밑에서 두번째 란에 ID와 주소를 넣으면 된다.

 

 

실행 결과>

 

 

esc 키 누르면 끝나는 지점에 마지막 Frame을 png 파일로 저장했다.

일정 시간마다 접속해서 저장만 하면 끝ㅋ

 

설정

트랙백

댓글

Python 셀레니움 이미지 크롤링

프로그래밍 언어 2020. 11. 2. 15:35

www.youtube.com/watch?v=1b7pXC1-IbE

 

와우~

설정

트랙백

댓글

C# RGB 출력, YUV420 -> RGB로 출력

프로그래밍 언어/C# 2020. 9. 1. 16:17

Raspberry Pi Zero W에 카메라 이미지를 PC에 전송하여 출력하는 것을 작업하고 있다.

 

그런데.... Raspberry Pi 에서 큰 문제가 생겼다...

 

자료를 찾아보니 대부분 OpenCV를 사용하여 데이터를 보내길래

 

OpenCV를 깔아봤는데.... 'Illegal instruction'이라고 에러가 떠서 동작이 안된다.

 

아무리 삽질을 해봐도 해결 방법을 못 찾았다ㅠㅠ;;

 

 

그래서 다른 방법을 찾던 중, 라즈베리 기본 설치 프로그램 중에

 

raspistill, raspivid, raspiyuv, raspividyuv를 사용해서 데이터를 보낼 방법을 찾았다.

 

 

 

라즈베리에서 카메라 실행은

raspividyuv -w 480 -h 320 -fps 5 -t 0 -l -o tcp://192.168.0.125:7777

이 명령 사용

 

YUV420 포멧으로 raw 데이터 보냄.

 

그래서 C#에서 화면에 그려줄때

480x320 사이즈로

한 프레임당 230400 bytes 이다.

 

0~153600 까지는 Y 값

153600~192000 까지는 U 값

192000~230400 까지 V 값

으로 구성되어 있다.

 

소스 ProcDrawingBuf() 함수에 38400 은 V값과 U 값 크기이다.

 

곳곳에 마이크 녹음이랑 사운드 출력도 있다....

 

 

--- MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Resources; // test.jpg
using System.IO.Ports; // SerialPort
using System.Windows.Threading; // DispatcherPriority
using System.Threading; // Thread
using NAudio.Wave;
using System.Drawing; // Bitmap
using System.IO;
using System.Net.Sockets; // TcpClient
using System.Net;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace YUVRGB_Ctrl
{
    ///
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// 
    public partial class MainWindow : System.Windows.Window
    {
        private SerialPort sp;  // COM 포트
        private bool portOpen;  // 포트 열림 여부

        private WaveIn sourceStream = null; // 녹음 핸들?

        private Thread t1; // 네트워크 스트림 thread
        private Thread t2; // 비트맵 thread
        private string destIP; // 원격지 IP
        private bool ipOpen; // 원격지 열기 닫기 여부

        private TcpClient tc; // 원격지 주소
        private NetworkStream stream; // 원격지 스트림

        private byte[] streamBuf = new byte[230400];
        private byte[] drawingBuf = new byte[230400];

        public MainWindow()
        {
            InitializeComponent();

            sp = null;
            portOpen = false;
            t1 = null;
            ipOpen = false;

            // 임시 이미지 그리기
            //BitmapImage drawImg = new BitmapImage(new Uri(@"C:\Users\root\Documents\Visual Studio 2017\Projects\KLabs RGControl\KLabs RGControl\test.jpg"));
            BitmapImage drawImg = new BitmapImage(new Uri("test.jpg", UriKind.Relative));
            imgTest.Source = drawImg;
            imgTest.Visibility = Visibility.Hidden;

            // 키보드 처리 인터럽트 추가
            this.KeyDown += new KeyEventHandler(FuncKeyDown);
        }

        // 키보드 처리
        private void FuncKeyDown(object sender, KeyEventArgs e)
        {
            switch(e.Key)
            {
                case Key.Escape:
                    this.Close();
                    break;
            }
        }

        // 메인 윈도우창 이동
        private void Window_MouseLeftBtnDown(object sender, RoutedEventArgs e)
        {
            Cursor = Cursors.SizeAll;
            this.DragMove();
        }

        private void Window_MouseLeftBtnUp(object sender, RoutedEventArgs e)
        {
            Cursor = Cursors.Arrow;
        }

        // exit 버튼
        private void BtnExit_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        // connect 버튼
        private void BtnConnect_Click(object sender, RoutedEventArgs e)
        {
            // 연결되어 있으면 기존 연결 끊기
            if (ipOpen == true)
            {
                // 스트림과 TcpClient 객체 닫기
                stream.Close();
                tc.Close();
                ipOpen = false;
            }

            Window1 infoWindown = new Window1();
            infoWindown.OnChildDataEvent += new Window1.OnChildDataHandler(GetDataFromChild);
            infoWindown.ShowDialog();
        }

        // 카메라, 센서 연결.
        private void GetDataFromChild(string destIP, string comPort)
        {
            // COM 포트 열기
            //OpenComport(comPort);

            // IP 카메라 연결
            OpenCam(destIP);
        }

        // COM 포트 열기
        private void OpenComport(string destCom)
        {
            Console.WriteLine(destCom);
            // COM 연결 상태 확인
            if(sp == null)
            {
                try
                {
                    // 1. SerialPort 클래스 객체 생성
                    sp = new SerialPort();

                    // 2. SerialPort 포트 셋팅 설정
                    sp.PortName = destCom;
                    sp.BaudRate = (int)115200;
                    sp.DataBits = 8;
                    sp.Parity = Parity.None;
                    sp.StopBits = StopBits.One;
                    sp.Handshake = Handshake.None;
                    sp.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);

                    // 3. 시리얼포트 오픈
                    sp.Open();
                    portOpen = true;
                }
                catch
                {
                    MessageBox.Show("COM 포트 열기 에러");
                }
            }
        }

        // COM 포트에서 읽은 데이터 처리
        public void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if ((portOpen == true) && (sp.BytesToRead >= 0))
            {
                try
                {
                    var stBulider = new StringBuilder();
                    stBulider.Append(sp.ReadLine());
                    //buf = sp.ReadLine();
                    if (stBulider[0] == '0')
                    {
                        stBulider.Append(sp.ReadLine());
                        stBulider.Append(sp.ReadLine());
                        stBulider.Append(sp.ReadLine());
                        stBulider.Append(sp.ReadLine());
                        /*
                        buf += sp.ReadLine();
                        buf += sp.ReadLine();
                        buf += sp.ReadLine();
                        buf += sp.ReadLine();
                        */
                        //buf = buf.Replace("\r", " ");

                        Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate
                        {
                            labelSensorStatus.Content = stBulider.ToString();
                        }));
                        //this.labelSerial.Content = buf;
                    }
                }
                catch
                { }
            }
        }

        // IP 카메라 연결
        private void OpenCam(string destIP2)
        {
            destIP = destIP2;
            Console.WriteLine(destIP2);
            imgTest.Visibility = Visibility.Visible;
            labelNetworkStatus.Visibility = Visibility.Hidden;

            // 카메라 데이터 스레드 처리
            /*
            if(t1 == null)
            {
                t1.Abort();
            }
            */
            int aTmp;

            t1 = new Thread(new ThreadStart(ProcCamData));
            t1.Start();
        }

        // IP 카메라 데이터 처리
        private void ProcCamData()
        {
            byte[] outbuf = new byte[15000];
            int nbytes;
            int total;
            int i, f;
            string output;
            string sTime = System.DateTime.Now.ToString("mm:ss.ffffff");
            TcpClient tc = new TcpClient("192.168.0.125", 7777);
            NetworkStream stream = tc.GetStream();

            total = 0;
            i = 0;
            f = 0;
            while(true)
            {
                nbytes = stream.Read(outbuf, 0, outbuf.Length);
                // 한 프레임(230400) 처리
                if (total + nbytes >= 230400)
                {
                    Array.Copy(outbuf, 0, streamBuf, total, 230400 - total);
                    Array.Copy(streamBuf, drawingBuf, 230400);
                    Array.Copy(outbuf, 230400 - total, streamBuf, 0, nbytes - (230400 - total));
                    if(t2 == null)
                    {
                        t2 = new Thread(new ThreadStart(ProcDrawingBuf));
                        t2.Start();
                    }
                    else if ((t2.ThreadState & ThreadState.Stopped) == ThreadState.Stopped)
                    {
                        t2 = new Thread(new ThreadStart(ProcDrawingBuf));
                        t2.Start();
                    }
                    total = nbytes - (230400 - total);
                    f++;
                }
                else
                {
                    Array.Copy(outbuf, 0, streamBuf, total, nbytes);
                    total += nbytes;
                }
                i++;
                //output = Encoding.ASCII.GetString(outbuf, 0, nbytes);
                //Console.WriteLine($"{i} : {nbytes} ");// bytes: {output}");

                if (nbytes == 0)
                {
                    break;
                }
            }
            Console.WriteLine($"total : {i} {f}");
            Console.WriteLine($"Start time : {sTime}");
            Console.WriteLine($"End time : {System.DateTime.Now.ToString("mm:ss.ffffff")}");

            stream.Close();
            tc.Close();
        }

        private void ProcDrawingBuf()
        {
            Bitmap bitmap = new Bitmap(480, 320);
            BitmapData bmpData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, 480, 320), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            int k, uv;
            int bY, bU, bV;

            k = 0;
            uv = 0;
            unsafe
            {
                byte* ptr = (byte*)bmpData.Scan0.ToPointer();
                for (int i = 0; i < 320; i++)
                {
                    for (int j = 0; j < 480; j++)
                    {
                        if (uv >= 38400)
                        {
                            break;
                        }
                        bU = drawingBuf[320 * 480 + uv] - 128;
                        bV = drawingBuf[320 * 480 + 38400 + uv] - 128;
                        bY = drawingBuf[k];

                        // R
                        if (bV < 0)
                            ptr[bmpData.Stride * i + 3 * j + 2] = (byte)bY;
                        else
                            ptr[bmpData.Stride * i + 3 * j + 2] = (byte)(bY + (2.0790 * bV));

                        // G
                        if (bU < 0)
                            ptr[bmpData.Stride * i + 3 * j + 0] = (byte)bY;
                        else
                            ptr[bmpData.Stride * i + 3 * j + 0] = (byte)(bY + (1.4075 * bU));

                        // B
                        ptr[bmpData.Stride * i + 3 * j + 1] = (byte)(bY - (0.237633 * bV) - (0.337633 * bU));

                        k++;
                        if (j % 2 == 0)
                        {
                            uv++;
                        }
                    }
                    if (i % 2 == 0)
                    {
                        uv -= 240;
                    }
                }
                bitmap.UnlockBits(bmpData);
            }

            Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate
            {
                imgTest.Source = BitmapToImageSource(bitmap);
            }));
            bitmap.Dispose();
        }

        public Bitmap ScreenCapture()
        {
            // 주화면의 크기 정보 읽기
            int width = (int)SystemParameters.PrimaryScreenWidth;
            int height = (int)SystemParameters.PrimaryScreenHeight;
            Bitmap scrbmp = new Bitmap(width, height);

            using (Graphics g = Graphics.FromImage(scrbmp))
            {
                g.CopyFromScreen(0, 0, 0, 0, scrbmp.Size, CopyPixelOperation.SourceCopy);
            }

            // Image에 캡처한 이미지를 뿌려주기 위해 Bitmap을 BitmapImage로 변환한다.
            using (MemoryStream memory = new MemoryStream())
            {
                scrbmp.Save(memory, ImageFormat.Bmp);
                memory.Position = 0;
                BitmapImage bitmapimage = new BitmapImage();
                bitmapimage.BeginInit();
                bitmapimage.StreamSource = memory;
                bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapimage.EndInit();
                imgTest.Source = bitmapimage;
            }

            return scrbmp;
        }

        BitmapImage BitmapToImageSource(Bitmap bitmap)
        {
            using (MemoryStream memory = new MemoryStream())
            {
                bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
                memory.Position = 0;
                BitmapImage bitmapimage = new BitmapImage();
                bitmapimage.BeginInit();
                bitmapimage.StreamSource = memory;
                bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapimage.EndInit();

                return bitmapimage;
            }
        }

        // 마이크 녹음 초기화
        public void RecodeMic()
        {
            sourceStream = new NAudio.Wave.WaveIn();
            sourceStream.BufferMilliseconds = 50;
            sourceStream.DeviceNumber = 0;
            sourceStream.WaveFormat = new NAudio.Wave.WaveFormat(4800, NAudio.Wave.WaveIn.GetCapabilities(0).Channels);
            sourceStream.DataAvailable += new EventHandler(sourceStream_DataAvailable);
            sourceStream.StartRecording();
        }

        // 마이크 녹음 데이터 처리
        private void sourceStream_DataAvailable(object sender, NAudio.Wave.WaveInEventArgs e)
        {
            byte[] encoded = e.Buffer;
            int i;
            float multiplier = 12.0f;   // Gain

            for (i = 0; i < encoded.Length; i = i + 2)
            {
                Int16 sample = BitConverter.ToInt16(encoded, i);
                sample = (Int16)(sample * multiplier);
                byte[] sampleBytes = BitConverter.GetBytes(sample);
                encoded[i] = sampleBytes[0];
                encoded[i + 1] = sampleBytes[1];
            }

            //encoded.CopyTo(recordingStream2, encoded.Length);
        }
    }
}

 

 

--- 실행화면

   - Raspberry pi zero w

 

   - PC

 

'프로그래밍 언어 > C#' 카테고리의 다른 글

C# 이미지 그리기, 출력  (0) 2020.08.21
C# NAudio MicroPhone 볼륨 조절  (0) 2020.08.20

설정

트랙백

댓글

C# 이미지 그리기, 출력

프로그래밍 언어/C# 2020. 8. 21. 14:16

그림을 출력할때, 다음과 같은 방법이 있다.

 

- 절대 경로

imgTest.Source = new BitmapImage(new Uri(@"C:\Image\test.png", UriKind.Absolute));

 

- 상대 경로

imgTest.Source = new BitmapImage(new Uri(@"\test.png", UriKind.Relative));

 

- 상대 경로(리소스)

imgTest.Source = new BitmapImage(new Uri(@"\test.png", UriKind.Relative));

 

imgTest는 이미지 도구상자 이름이다.

BitmapImage 2번째 인자는

https://docs.microsoft.com/ko-kr/dotnet/api/system.urikind?view=netcore-3.1

 

 

- 이렇게 저장해서 필요할 때 쓰기도...

BitmapImage bitTmp = new BitmapImage(new Uri(@"\Image\1.png", UriKind.Relative));

imgTeset.Source = bitTmp;

 

 

- 리소스 방식을 쓸때는 리소스에 추가해서 써야한다.

 

    리소스 관리 페이지로 들어가서

 

    리소스를 추가하고

 

    추가된 파일의 속성을 바꾸면 끝.

 

'프로그래밍 언어 > C#' 카테고리의 다른 글

C# RGB 출력, YUV420 -> RGB로 출력  (0) 2020.09.01
C# NAudio MicroPhone 볼륨 조절  (0) 2020.08.20

설정

트랙백

댓글

C# NAudio MicroPhone 볼륨 조절

프로그래밍 언어/C# 2020. 8. 20. 21:07

private void sourceStream_DataAvailable(object sender, NAudio.Wave.WaveInEventArgs e)

{

    byte[] encoded = e.Buffer;

    if (reLen + encoded.Length < 70000)

    {

        if (recordingFirstSkip > 6)

        {

            encoded.CopyTo(recordingStream, reLen);

            int i;

           float multiplier = 12.0f; // 볼륨 Gain

           for (i = 0; i < encoded.Length; i = i + 2)

           {

               Int16 sample = BitConverter.ToInt16(encoded, i);

               sample = (Int16)(sample * multiplier);

               byte[] sampleBytes = BitConverter.GetBytes(sample);

               encoded[i] = sampleBytes[0];

               encoded[i + 1] = sampleBytes[1];

            }

            encoded.CopyTo(recordingStream2, reLen);

            reLen += encoded.Length;

           }

          else

         {

              encoded.CopyTo(recordingStream, reLen);

              encoded.CopyTo(recordingStream2, reLen);

              recordingFirstSkip++;

         }

    }

}

 

 

 

 

'프로그래밍 언어 > C#' 카테고리의 다른 글

C# RGB 출력, YUV420 -> RGB로 출력  (0) 2020.09.01
C# 이미지 그리기, 출력  (0) 2020.08.21

설정

트랙백

댓글

Visual Studio 2019 한글 형식(인코딩). 유니코드, EUC-KR

프로그래밍 언어/WinApi 2020. 7. 28. 10:17

문자를 입력 받았을 경우, 한글인지 아닌지 판별하기 위하여 작업 도중,

 

WCHAR, char의 한글 형식을 같은 유니코드를 사용하고 있는 줄 알았으나

 

값이 다르게 나와서 찾아 보았다.

 

 

한글의 형식은 컴파일러마다, 운영체제마다 다르다고 한다.

 

Window10, Visual Studio 2019의 환경에서

 

WCHAR 형은 유니코드를 사용하고

 

char 형은 EUC-KR을 사용하는 것 같다.

 

ex)

    //WCHAR eee[20] = TEXT("가나하");
    unsigned char eee[20] = "가나하";
    unsigned char eee1;
    unsigned char eee2;

    eee1 = *((unsigned char*)eee + 0); 
    eee2 = *(((unsigned char*)eee) + 1); 
    TextMB::Box(eee1, eee2);

    eee1 = *((unsigned char*)eee + 2);
    eee2 = *(((unsigned char*)eee) + 3);
    TextMB::Box(eee1, eee2);

    eee1 = *((unsigned char*)eee + 4);
    eee2 = *(((unsigned char*)eee) + 5);
    TextMB::Box(eee1, eee2);

 

  유니코드 WCHAR char
AC00 00AC B0A1
B098 98B0 B3AA
D558 58D5 C7CF

 

둘 다, 두번째 바이트에 최상위 비트가 1인지 0인지 확인하면 한글 판별이 가능할 것 같다.

 

그런데 WCHAR의 값은 리틀엔디안 형식이니 반대로 나온게 맞는것 같은데,

char의 값은 그대로 나왔다. 뭐지..?

 

-- 유니코드 문자표

https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_%EC%98%81%EC%97%AD

https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_A000~AFFF

 

-- EUC-KR 문자표

https://uic.win/ko/charset/show/euc-kr/

 

-- CP949

https://charset.fandom.com/ko/wiki/CP949

 

 

EUC-KR를 찾다가 보니 CP949, KS X 1001, KS X 1003 등등 여러 인코딩이 나오던데

언급은 하지 않겠다...

https://codingdog.tistory.com/entry/cp949-vs-euc-kr-%EC%96%B4%EB%96%A4-%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%9D%B4-%EC%9E%88%EB%8A%94%EC%A7%80-%EA%B0%84%EB%8B%A8%ED%9E%88-%EC%95%8C%EC%95%84%EB%B4%85%EC%8B%9C%EB%8B%A4

설정

트랙백

댓글

win32api 서브클래싱, 전역클래싱, 슈퍼클래싱

프로그래밍 언어/WinApi 2020. 7. 27. 13:23

Tab(탭) 키로 각 차일드 윈도우간 포커스 이동을 하려고 했더니 클래싱을 사용해야할 것 같다.

 

# 서브클래싱

서브클래싱이란 도스의 인터럽트 가로채기와 개념적으로 동일하며 윈도우 프로시저로 전달되는 메시지를 중간에
가로채는 기법이다.

운영체제 -> 서브클래스 프로시저 -> 윈도우 프로시저

서브클래스 프로시저는 메시지를 먼저 받아서 다음의 세가지 방법으로 이 메시지를 처리할 수 있다.
① 통과 : 관심 없는 메시지는 바로 윈도우 프로시저로 넘겨버린다.
② 직접 처리 : 원하는 메시지가 왔을때 중간에 메시지를 처리하고 리턴. 윈도우 프로시저로는 전달되지않는다.
③ 변형 : 메시지를 받아서  원하는 변경 후 윈도우 프로시저로 보낸다. (키입력의 변경등)


새로운 윈도우 프로시저를 생성해서 아래 함수를 통해 변경할 대상 윈도우의 새로운 메시지 처리 함수를 지정한다.
원래번지=SetWindowLongPtr(변경할 대상윈도우,GWLP_WNDPROC,서브클래스 프로시저)서브 클래스 프로시저에서는 원하는 메시지 이외에는 다시 윈도우 프로시저로 넘겨줘야 하기때문에 관심없는
메시지에 대해서는 프로시저 마지막에 아래 함수를 사용하여 메시지를 다시 윈도우 프로시저로 넘겨주면된다.
LRESULT CallWindowProc(WNDPRCO lpPrevWndFunc,HWND hWnd,UINT Msg,WPARAM wParam,
LPARAM lParam);
메시지를 마음대로 변경할 수 있는 서브클래싱은 아주 유용하고도 강력한 기법임에 틀림없지만 그 이면에는 항상
잠재적인 위험성이 도사리고 있어 아주 조심스럽게 사용해야 한다. 꼭 필요하지 않는 한 서브클래싱은 하지 않는
것이 좋지만 불가피할 경우는 다음의 주의사항을 잘 숙지하고 안전하게 사용하자.

1. 윈도우의 원래 기능을 보존할 것.
2. 필요한 메시지를 제외한 메시지는 온전하게 윈도우 프로시저로 보낼 것.
3. 여분 메모리(cbWndExtra,cbClsExtra)를 절대로 건드리지 말것.
4. 여분 메모리가 필요한 경우 윈도우 프로퍼티를 사용할 것.

 

 

예제)

 

HWND hSub;        // 서브클래싱할 윈도우 핸들
WNDPROC wp_OldSubProc;        // 서브클래싱하기 전의 윈도우 프로시져 주소

 

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) 
{ 
    ... 
    WNDCLASS WndClass;


    switch (iMessage)

    { 
    case WM_CREATE: 

 

    ...

 

        // 프로시저 변경

        wp_OldSubProc = (WNDPROC)SetWindowLong( hSub, GWL_WNDPROC, (LONG)Test1Proc );

 

    ...

 

        // 프로그램 종료시 다시 원래의 프로시져 주소로 교체 해준다.
        SetWindowLong( hSub, GWL_WNDPROC, (LONG)wp_OldSubProc );

    ...

}

 

 

LRESULT CALLBACK Test1Proc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    switch( iMessage )
    {
    case WM_KEYDOWN:
        break;
    }

    return CallWindowProc( wp_OldSubProc, hWnd, iMessage, wParam, lParam);
}




# 전역 서브클래싱

서브 클래싱도 시점과 방법, 그리고 효과가 미치는 범위에 따라 다음 두 가지로 분류된다.

■ 인스턴스 서브클래싱 : 특정한 하나의 윈도우 만을 대상으로 서브클래싱, SetWindowLongPtr 사용된다.

■ 전역 서브클래싱 : 윈도우 클래스에 대해 서브 클래싱, 이미 만들어진 윈도우에 대해서는 효과가 없으며 앞으로
  만들어질 윈도우만 영향을 받는다. SetClassLongPtr이 사용된다.


에디트를 생성하고 에디트를 대상으로 전역 서브 클래싱을 할 경우 에디트가 속한 "edit"윈도우 클래스가
서브 클래싱의 대상
이 되므로 서브 클래싱한 이 후 생겨나는 모든 edit는 서브 클래싱의 영향을 받게된다.

SetClassLongPtr(hEdit1,GCLP_WNDPROC,(LONG_PTR)OldEditProc);전역 클래싱은 윈도우 클래스 자체를 바꾸는 것이지만 다른 프로세스에는 영향을 주지 못한다.
시스템 전역 클래스에 정보는 시스템이 가지고 있지만 각 프로세스가 참조하는 클래스 정보는 응용프로그램이
시작될 때 시스템으로 전달받는 복사본일 뿐이다. 즉 서브클래싱은 같은 프로세스 내에서만 효력을 발휘한다.


# 슈퍼클래싱

슈퍼클래싱은 기존클래스(=베이스 클래스)의 정보를 바탕으로 하여 완전히 새로운 클래스를 만드는 것이다.
슈퍼클래싱에 의해 새로 만들어진 윈도우 클래스는 베이스 클래스와 동일하지만 여기에 변화를 주면 원하는대로
윈도우 클래스를 수정할 수 있다.

슈퍼클래싱의 핵심 함수는 GetClassInfo(Ex)함수이다.

BOOL GetClassInfo(HINSTANCE hInstance,LPCTSTR lpClassName,LPWNDCLASS lpWndClass);이 함수는 lpClassName 윈도우 클래스의 정보를 조사하여 lpWndClass가 가리키는 WNDCLASS 구조체에
채운다. 첫번 째 인수는 이 윈도우 클래스를 만든 응용프로그램의 인스턴스 핸들이되 버튼, 에디트 등의 시스템에
의해 만들어진 윈도우 클래스일 경우는 NULL을 주면 된다. 이 함수 호출에 의해 윈도우 클래스의 정보를 조사할
수 있는데 예를 들자면 GetClassInfo(NULL,"edit",&WndClass); 함수를 호출하면 "edit" 윈도우 클래스의 정보를
WndClass 구조체에 복사한다. 이 함수는 등록된 윈도우 클래스의 정보를 다시 돌려받으므로 RegisterClass함수의
반대 함수라고 생각하면 쉽다.

WndClass 구조체에 윈도우 클래스의 정보가 조사되었으므로 이 구조체의 정보를 바탕으로 적절히 수정한 후 다시
RegisterClass로 새로운 윈도우 클래스를 등록하는 것이 바로 슈퍼클래싱이다.

원본(GetClassInfo) ->  사본(수정) -> 새로등록(RegisterClass)

WndClass 구조체의 정보중 몇가지는 반드시 수정되어야 한다. 일단 클래스 이름이 베이스 클래스와 같아서는
안되므로 lpszClassName 멤버가 달라져야 한다. 또한 hInstance 멤버에는 이 윈도우 클래스를 등록하는 응용
프로그램의 인스턴스 핸들을 대입해야 하는데 그래야 이 인스턴스가 종료될 때 등록해제 된다. 그리고 가장 중요한
lpfnWndProc멤버를 수정하여 응용 프로그램이 정의한 윈도우 프로시저로 메시지를 보내도록 해야 할 것이다.
단 이때 기존의 윈도우 프로시저 번지는 복구를 위해 잘 보관해 두어야 한다.


[슈퍼 클래싱 예제]
LRESULT CALLBACK SuperProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    switch (iMessage)
    {
    ...
    }
    return CallWindowProc(OldEditProc,hWnd,iMessage,wParam,lParam);
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    ...
    WNDCLASS WndClass;


    switch (iMessage)

    {
    case WM_CREATE:
        GetClassInfo(NULL,TEXT("edit"),&WndClass); // "edit"클래스에 대한 정보 조사
        WndClass.hInstance=g_hInst;
        WndClass.lpszClassName=TEXT("SuperEdit"); // 새로운 이름 등록
        WndClass.hCursor=LoadCursor(NULL,IDC_WAIT); // 커서 변경
        OldEditProc=WndClass.lpfnWndProc; // 기존의 윈도우 프로시저 저장
        WndClass.lpfnWndProc=SuperProc; // 새로운 프로시저로 변경
        RegisterClass(&WndClass); // 수정한 윈도우 등록
        hEdit1=CreateWindow(TEXT("SuperEdit"),NULL,WS_CHILD | WS_VISIBLE |
            WS_BORDER | ES_AUTOHSCROLL,
            10,10,200,25,hWnd,(HMENU)ID_EDIT1,g_hInst,NULL);
        SetFocus(hEdit1);
        return 0;

    ...
}

 

서브 클래싱과 슈퍼클래싱은 둘다 사용목적이 비슷하며 상호 대체성이 있다. 어떤 방법을 쓸 것인가는 상황에
따라 다르겠지만 대체로 슈퍼클래싱이 더 안전하고 부릴 수 있는 기교도 더 많다.
특정 윈도우 하나만 수정할 때는 인스턴스 서브클래싱을 사용하고 다량의 윈도우를 한꺼번에 바꿀 때는 전역 또는
슈퍼클래싱을 사용하는 것이 편리하다.

 

 

 

 

참고 사이트 : http://egloos.zum.com/itbaby/v/4513218

참고 사이트 : http://blog.daum.net/creazier/15309359

설정

트랙백

댓글

Visual Studio 2019 win32api 아이콘 변경이 안된다

프로그래밍 언어/WinApi 2020. 7. 22. 13:32

리소스에 아이콘 추가 후,

 

메인 클래스에

 

wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_ICON1));

 

를 수정해도 탐색기에 표시되는 아이콘이 변경되지 않았다.

 

 

자료를 찾아도 MFC, C#은 나와있지만 win32api는 없었다.

아마 내부적으로 캐시된게 잘 처리가 안되지 않았을까... 라고 누가 그러더라..

아래와 같이 다시 불러와 주니 바뀌었다.

 

 

프로젝트에서 기존항목을 누르고 변경할 아이콘을 선택한 후에 컴파일을 하니 바뀌었다.

 

다시 해보니 안된다....

 

껐다 키고 뭔짓을 해야된다.....

설정

트랙백

댓글

Visual Studio 2019 sprintf 에러 끄기

프로그래밍 언어/WinApi 2020. 7. 22. 12:05

Visual Studio 2019에서 컴파일시, sprintf 를 사용할 수 없음으로 에러가 뜰 경우

 

심각도 코드 설명 프로젝트 파일 줄 비표시 오류(Suppression) 상태
오류 C4996 'sprintf': This function or variable may be unsafe. Consider using sprintf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. MP_works C:\Users\root\Documents\Visual Studio 2019\Projects\MP_works\MP_works\MP_works.cpp

 

 

 

 

아래와 같이 대처

---> 메뉴에 프로젝트 -> 속성 -> 왼쪽 메뉴에 구성속성 -> C/C++ -> 일반 -> SDL검사를 아니오로 변경

 

 

 

설정

트랙백

댓글

C언어) 간단하게 엑셀 파일로 저장하기

프로그래밍 언어 2019. 7. 10. 09:32

저런식으로 ,(콤마)로 칸을 구별하여 엑셀 파일로 저장할 수 있다.

확장자를 csv로 저장해야 한다.

설정

트랙백

댓글