Microsoft MVP성태의 닷넷 이야기
DDK: 3. NT Legacy 드라이버를 이용하여 C#에서 Port 입출력 [링크 복사], [링크+제목 복사],
조회: 28968
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
[PortIO.zip]    
(연관된 글이 1개 있습니다.)
(시리즈 글이 5개 있습니다.)
DDK: 3. NT Legacy 드라이버를 이용하여 C#에서 Port 입출력
; https://www.sysnet.pe.kr/2/0/932

DDK: 5. NT Legacy 드라이버: 프로세스(EXE) 생성/제거 모니터링
; https://www.sysnet.pe.kr/2/0/937

DDK: 6. ZwTerminateProcess로 프로세스를 종료하는 Device Driver 프로그램
; https://www.sysnet.pe.kr/2/0/2904

DDK: 8. Visual Studio 2019 + WDK Legacy Driver 제작- Hello World 예제
; https://www.sysnet.pe.kr/2/0/12103

DDK: 9. 커널 메모리를 읽고 쓰는 NT Legacy driver와 C# 클라이언트 프로그램
; https://www.sysnet.pe.kr/2/0/12104




NT Legacy 드라이버를 이용하여 C#에서 Port 입출력

요즘 보고 있는 책이 있는데요. ^^

IT EXPERT 윈도우 디바이스 드라이버 (저자 : 이봉석)
; http://www.yes24.com/24/goods/3219701

무척 재미있게 보고 있습니다. 현재 물리적인 "장치"와는 직접적인 인연이 없기 때문에, 제가 원하는 것은 "NT 구형(Legacy) 드라이버" 유형 정도면 딱 좋은 것 같습니다.

그 책에서는 많은 부분을 다루고 있지만 제 경우에는 순수하게 응용 프로그램에서 전달할 IRP만을 고려하면 되었으므로 IRP 취소나 IRQL 레벨에 너무 구애받지 않아도 된다는 가정이 성립되어 간단한 실습까지 해보았습니다. 원래는 그 책의 "실습 5"를 해보려고 했는데요. IOPM(IO Permission Bitmap) 자체가 16비트 도스 응용 프로그램들에 대한 호환을 고려하여 나온 것이라서 16비트 지원 코드가 삭제된 x64 버전의 윈도우 운영체제에서는 덩달아 같이 없어졌기 때문에 포기해야만 했습니다. 대신에, 포트 번호를 지정해서 I/O를 대신할 수 있는 IOCTL 기능을 제공하는 것으로 만족해야만 했습니다.

코드 설명을 같이 해볼까 했는데, 이미 아래와 같은 글들에서 너무나 훌륭하게 설명하고 있어서 ^^; 더 할 이야기가 없더군요.

Kport Part 2: Direct Access I/O Ports under Win NT/2000/XP
; http://www.codeproject.com/KB/system/kportII.aspx?msg=3132239

위의 글에서도 역시 IOPM을 다루고 있지만, 동시에 Outportb / Inportb 코드까지 모두 같이 제공해주고 있습니다. 그래서, 저 같은 경우에는 위의 소스 코드에서 IOPM 관련 코드를 제거하고 Outportb / Inportb만 구현한 간단한 "NT Legacy 드라이버"를 빌드해 보았습니다.

예전에는, 막연히 드라이버 관련 코드들에 거부감이 생기더니만 "IT EXPERT 윈도우 디바이스 드라이버 (저자 : 이봉석)" 책의 도움으로 이젠 눈에 보이기 시작했습니다. 해보고 나니, 포트 I/O 하는 것이 ^^; 가장 쉬운 예제라는!

.sys 드라이버는 제작했는데, C/C++에서 접근하는 방식 대신 C#에서 P/Invoke로 접근하는 코드를 작성해 보았습니다. 사실 CreateFile / DeviceIoControl 관련 P/Invoke 코드들이 다음의 사이트에 이미 공개되어 있기 때문에 이마저도 어렵지 않았습니다.

Kernel32.cs 
; http://www.koders.com/csharp/fid27A6C02740D82511E33FEDF941EE0D26C84E45E6.aspx

그래서, 다음과 같은 식으로 C#에서 포트 입출력을 할 수 있는 클래스를 만들 수 있게 되었습니다.

class PortIOLib : IDisposable
{
    const uint IOCTL_READ_PORT_UCHAR = 0x9c402410;
    const uint IOCTL_WRITE_PORT_UCHAR = 0x9c402414;

    SafeFileHandle fileHandle;

    public PortIOLib()
    {
    }

    public PortIOLib(bool initialize)
    {
        if (initialize == true)
        {
            InitializePortIO();
        }
    }

    public bool InitializePortIO()
    {
        Dispose();

        fileHandle = Kernel32.CreateFile(@"\\.\PortIO", NativeFileAccess.FILE_GENERIC_READ,
            NativeFileShare.NONE, IntPtr.Zero, NativeFileMode.OPEN_EXISTING, NativeFileFlag.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

        if (fileHandle.IsInvalid == true)
        {
            return false;
        }

        return true;
    }

    public void Dispose()
    {
        if (fileHandle != null)
        {
            fileHandle.Close();
            fileHandle = null;
        }
    }

    public byte Inportb(ushort portNumber, out int errorNo)
    {
        errorNo = 0;
        if (fileHandle == null || fileHandle.IsInvalid == true)
        {
            errorNo = -1;
            return 0;
        }

        byte [] portBytes = BitConverter.GetBytes(portNumber);
        int pBytesReturned;

        if (Kernel32.DeviceIoControl(fileHandle, IOCTL_READ_PORT_UCHAR, portBytes, portBytes.Length, portBytes, portBytes.Length,
            out pBytesReturned, IntPtr.Zero) == true)
        {
            return portBytes[0];
        }

        errorNo = Marshal.GetLastWin32Error();
        return 0;
    }

    public bool Outportb(ushort portNumber, byte data)
    {
        if (fileHandle == null || fileHandle.IsInvalid == true)
        {
            return false;
        }

        byte[] portBytes = BitConverter.GetBytes(portNumber);

        byte[] inBuffer = new byte[3] { portBytes[0], portBytes[1], data };
        int pBytesReturned;

        return Kernel32.DeviceIoControl(fileHandle, IOCTL_WRITE_PORT_UCHAR, inBuffer, inBuffer.Length, inBuffer, inBuffer.Length,
            out pBytesReturned, IntPtr.Zero);
    }
}

이렇게 만들어진 걸로 테스트를 해봐야겠는데... 음... 어떤 걸로 해볼까... 하다가 다음의 글이 생각났습니다.

한 대의 PC에서 여러 개의 키입력 매크로 프로그램이 가능할까?
; https://www.sysnet.pe.kr/2/0/848

위의 글에서는 포트 입출력을 위해 "NTPort"라는 라이브러리를 이용하였었는데요. 와~~~ ^^ 이제는 그 의존성을 제거할 수 있게 되었습니다. 그래서, 다음과 같이 작성할 수 있게 되었고, 실제로 동작까지도 아주 잘 됩니다. ^^

class Program
{
    static void Main(string[] args)
    {
        using (PortIOLib portIoLib = new PortIOLib(true))
        {
            int count = 0;
            while (true)
            {
                Thread.Sleep(3 * 1000);
                if (kbc_input_buffer_empty(portIoLib) == false)
                {
                    break;
                }

                portIoLib.Outportb(0x64, 0xd2);

                if (kbc_input_buffer_empty(portIoLib) == false)
                {
                    break;
                }

                portIoLib.Outportb(0x60, 10);
                Console.WriteLine(count++);
            }
        }
    }

    static bool kbc_input_buffer_empty(PortIOLib portLib)
    {
        int timeout;
        int errorNo;

        for (timeout = 200; timeout > 0; timeout--) 
        {
            byte inByte = portLib.Inportb(0x64, out errorNo);
            if (errorNo != 0)
            {
                return false;
            }

            if ((inByte & 2) != 2)
            {
                break;
            }

            Thread.Sleep(1);
        }

        bool result = timeout != 0;
        if (result != true)
        {
            Console.WriteLine("ibuffer empty failed!");
        }

        return result;
    }
}

첨부 파일은, 제가 실습한 Device Driver 소스와 C# 소스를 포함한 솔루션 프로젝트입니다.




참고로,

  • 위의 예는 실습일 뿐, Port I/O를 사용자 응용 프로그램 레벨에 허용하는 것은 보안 위협에 해당하니 위의 코드를 현업에서 그대로 사용하는 일은 없길 바랍니다.
  • 위의 코드를 Windows 7 x64 또는 Windows Server 2008 R2에서 실습하려면 (정식 서명된 인증서를 사용하거나) 테스트 인증서 서명과 함께 윈도우 운영체제를 "Test Mode"로 전환해야 합니다.
  • 실습을 위해 portio32.sys(portio64.sys) 파일을 등록하는 작업은 sc.exe 또는 Driver Loader와 같은 프로그램을 이용하시면 됩니다.
  • 등록 과정은 Service Control Manager를 다루는 Win32 API를 이용하여 내부적으로 통합해도 되지만, Vista 이상의 운영체제에서는 "관리자 권한"을 필요로 하므로 현실적으로 볼 때 "제품 설치" 과정에서 해주시는 것이 좋습니다.




[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/6/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... [76]  77  78  79  80  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
12036정성태10/14/201925434.NET Framework: 866. C# - 고성능이 필요한 환경에서 GC가 발생하지 않는 네이티브 힙 사용파일 다운로드1
12035정성태10/13/201919576개발 환경 구성: 461. C# 8.0의 #nulable 관련 특성을 .NET Framework 프로젝트에서 사용하는 방법 [2]파일 다운로드1
12034정성태10/12/201918893개발 환경 구성: 460. .NET Core 환경에서 (프로젝트가 아닌) C# 코드 파일을 입력으로 컴파일하는 방법 [1]
12033정성태10/11/201923091개발 환경 구성: 459. .NET Framework 프로젝트에서 C# 8.0/9.0 컴파일러를 사용하는 방법
12032정성태10/8/201919217.NET Framework: 865. .NET Core 2.2/3.0 웹 프로젝트를 IIS에서 호스팅(Inproc, out-of-proc)하는 방법 - AspNetCoreModuleV2 소개
12031정성태10/7/201916478오류 유형: 569. Azure Site Extension 업그레이드 시 "System.IO.IOException: There is not enough space on the disk" 예외 발생
12030정성태10/5/201923278.NET Framework: 864. .NET Conf 2019 Korea - "닷넷 17년의 변화 정리 및 닷넷 코어 3.0" 발표 자료 [1]파일 다운로드1
12029정성태9/27/201924107제니퍼 .NET: 29. Jennifersoft provides a trial promotion on its APM solution such as JENNIFER, PHP, and .NET in 2019 and shares the examples of their application.
12028정성태9/26/201919062.NET Framework: 863. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상을 해결하기 위한 시도파일 다운로드1
12027정성태9/26/201914807오류 유형: 568. Consider app.config remapping of assembly "..." from Version "..." [...] to Version "..." [...] to solve conflict and get rid of warning.
12026정성태9/26/201920231.NET Framework: 862. C# - Active Directory의 LDAP 경로 및 정보 조회
12025정성태9/25/201918537제니퍼 .NET: 28. APM 솔루션 제니퍼, PHP, .NET 무료 사용 프로모션 2019 및 적용 사례 (8) [1]
12024정성태9/20/201920449.NET Framework: 861. HttpClient와 HttpClientHandler의 관계 [2]
12023정성태9/18/201920914.NET Framework: 860. ServicePointManager.DefaultConnectionLimit와 HttpClient의 관계파일 다운로드1
12022정성태9/12/201924860개발 환경 구성: 458. C# 8.0 (Preview) 신규 문법을 위한 개발 환경 구성 [3]
12021정성태9/12/201940660도서: 시작하세요! C# 8.0 프로그래밍 [4]
12020정성태9/11/201923838VC++: 134. SYSTEMTIME 값 기준으로 특정 시간이 지났는지를 판단하는 함수
12019정성태9/11/201917389Linux: 23. .NET Core + 리눅스 환경에서 Environment.CurrentDirectory 접근 시 주의 사항
12018정성태9/11/201916188오류 유형: 567. IIS - Unrecognized attribute 'targetFramework'. Note that attribute names are case-sensitive. (D:\lowSite4\web.config line 11)
12017정성태9/11/201920004오류 유형: 566. 비주얼 스튜디오 - Failed to register URL "http://localhost:6879/" for site "..." application "/". Error description: Access is denied. (0x80070005)
12016정성태9/5/201920012오류 유형: 565. git fetch - warning: 'C:\ProgramData/Git/config' has a dubious owner: '(unknown)'.
12015정성태9/3/201925406개발 환경 구성: 457. 윈도우 응용 프로그램의 Socket 연결 시 time-out 시간 제어
12014정성태9/3/201919158개발 환경 구성: 456. 명령행에서 AWS, Azure 등의 원격 저장소에 파일 관리하는 방법 - cyberduck/duck 소개
12013정성태8/28/201922066개발 환경 구성: 455. 윈도우에서 (테스트) 인증서 파일 만드는 방법 [3]
12012정성태8/28/201926624.NET Framework: 859. C# - HttpListener를 이용한 HTTPS 통신 방법
12011정성태8/27/201926220사물인터넷: 57. C# - Rapsberry Pi Zero W와 PC 간 Bluetooth 통신 예제 코드파일 다운로드1
... [76]  77  78  79  80  81  82  83  84  85  86  87  88  89  90  ...