Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

eBEST XingAPI의 C# 래퍼 버전 - XingAPINet Nuget 패키지

C# 래퍼 버전을 만들어 NuGet에 배포했습니다. 얼마나 사용 방법이 쉬운지 ^^ 한 번 작성해 볼까요?

예제를 위해 .NET Framework 4.0 이상의 프로젝트를 만들고, Target Platform을 "AnyCPU"에서 "x86"으로 맞춰줍니다. (왜냐하면, 증권사 API가 64비트를 지원하지 않습니다.)

how_to_use_xing_1.png

그다음, NuGet으로부터 XingAPINet 라이브러리를 참조 추가하면,

Install-Package XingAPINet

끝입니다! ^^

그런데... 제가 주식 분야는 거의 몰라서 ^^ 할 줄 아는 게 없으니 그냥 종목 코드를 가져오는 예제를 작성해 보겠습니다. XingAPI에서 종목 코드를 구할 수 있는 API는 t8430이므로, 따라서 다음과 같이 코딩을 하면 됩니다.

LoginInfo user = LoginInfo.FromPlainText("...id...", "...password...",
        "...인증서 password..."); // 만약 demo 서버 접속이라면 인증서 계정은 필요 없습니다.

using (XingClient xing = new XingClient(true)) // true == demo.etrade.co.kr, false == hts.etrade.co.kr
{
    if (xing.ConnectWithLogin(user) == false)  // 서버 접속
    {
        Console.WriteLine(xing.ErrorMessage);
        return;
    }

    // Query이므로 t8430에 "XQ" 접두사가 붙은 타입을 사용
    XQt8430OutBlock [] items = XQt8430.Get('1'); // 1 == 코스피 종목
    foreach (var item in items)
    {
        Console.WriteLine(item.uplmtprice); // 상한가 출력

        // item.Dump(Console.Out, DumpOutputType.Inline80Cols);
    }
}

오~~~ 엄청 간단합니다. 하는 김에 t8425로 가져오는 테마주도 다뤄볼까요? ^^ 위의 코드에 다음과 같은 코드만 추가하면,

using (XingClient xing = new XingClient(true))
{
    // ...[생략]...

    XQt8425OutBlock [] items2 = XQt8425.Get();
    foreach (var item in items2)
    {
        item.Dump(Console.Out, DumpOutputType.Inline);
    }
}

테마주의 목록이 화면에 출력됩니다. 게다가 모든 타입에 속성으로 API의 필드까지 모두 포함되어 있어 문자열을 하드 코딩할 필요 없이 인텔리센스의 도움을 받아 가며 작업할 수 있습니다. ^^




프로그램을 빌드하면 출력 폴더로 eBEST의 xingAPI 패키지에 포함된 바이너리가 함께 출력됩니다.

how_to_use_xing_2.png

또한, 예전에 키움 OpenAPI처럼,

C# - 키움 Open API+ 사용 시 Registry 등록 없이 KHOpenAPI.ocx 사용하는 방법
; https://www.sysnet.pe.kr/2/0/12129

app.manifest 파일을 추가해 다음의 내용으로 채워주면 말 그대로 XCopy 배포 방식도 가능합니다. 즉, COM 객체 등록도 필요가 없는 것입니다. (만약 이 파일을 추가하지 않으면 빌드 출력 폴더의 reg.bat 파일을 관리자 권한으로 실행해야 합니다.)

<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
        <security>
            <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
                <requestedExecutionLevel level="asInvoker" uiAccess="false" />
            </requestedPrivileges>
        </security>
    </trustInfo>

    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
        </application>
    </compatibility>

    <dependency>
        <dependentAssembly asmv2:dependencyType="install" asmv2:codebase="XA_Session.dll.manifest" asmv2:size="1035">
            <assemblyIdentity name="XA_Session.dll" version="1.0.0.1" processorArchitecture="x86" type="win32" />
        </dependentAssembly>
    </dependency>

    <dependency>
        <dependentAssembly asmv2:dependencyType="install" asmv2:codebase="XA_DataSet.dll.manifest" asmv2:size="1654">
            <assemblyIdentity name="XA_DataSet.dll" version="1.0.0.1" processorArchitecture="x86" type="win32" />
        </dependentAssembly>
    </dependency>

</asmv1:assembly>

이 때문에, eBEST의 데모 서버에 접속하는 용도라면 인증서까지도 필요 없기 때문에 아무 PC에나 .NET Frmaework 4.0만 설치되어 있다면 그대로 복사해 실행하는 것이 가능합니다. 물론, 실 서버에 접속한다면 공인 인증서가 설치된 PC이기만 하면 됩니다.




직접 해보니, 키움 API보다 eBEST의 XingAPI가 더 좋습니다. 왜냐하면 API 단에서 로그인할 수 있는 방법도 제공하기 때문에 "NT 서비스" 형식의 프로그램도 만들 수 있어 컴퓨터에 수동으로 직접 로그인하지 않아도 시스템 트레이딩 작업이 가능합니다. (혹은 autohotkey 같은 꼼수나 기타 UAC를 꺼서 보안 위협이 될 만한 조치가 전혀 필요 없습니다.) 여전히 Unicode 지원이 되지 않는다는 정도의 단점은 있지만 그래도 이 정도면 키움에 비해서는 훌륭합니다. ^^

또한, 해당 API들의 명세를 "RES" 확장자 파일로 제공하는데 제법 규격화가 잘 되어 있습니다. 예를 들어 아래는 t0167.res 파일의 내용인데,

BEGIN_FUNCTION_MAP
    .Func,서버시간조회(t0167),t0167,block,headtype=A;
    BEGIN_DATA_MAP
    t0167InBlock,기본입력,input;
    begin
        id,id,id,char,8;
    end
    t0167OutBlock,출력,output;
    begin
        일자(YYYYMMDD),dt,dt,char,8;
        시간(HHMMSSssssss),time,time,char,12;
    end
    END_DATA_MAP
END_FUNCTION_MAP

제가 만든 Res2Query 프로젝트는 위의 RES 파일을 읽어들여 다음과 같은 형식의 C# 소스 코드 파일을 생성합니다. (그리고 이 파일은 XingAPINet 바이너리에 포함됩니다.)

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using XingAPINet;

namespace XingAPINet
{
    public partial class XQt0167InBlock : XingBlock
    {
        /// <summary>
        /// t0167InBlock
        /// </summary>
        public const string _blockName = "t0167InBlock";
        /// <summary>
        /// 기본입력
        /// </summary>
        public const string _blockDesc = "기본입력";
        /// <summary>
        /// input
        /// </summary>
        public const string _blockType = "input";
        /// <summary>
        /// false
        /// </summary>
        public const bool _hasOccurs = false;
        /// <summary>
        /// t0167InBlock
        /// </summary>
        public override string GetBlockName() => _blockName;
        /// <summary>
        /// t0167InBlock
        /// </summary>
        public static string BlockName => _blockName;
        /// <summary>
        /// 기본입력
        /// </summary>
        public string BlockDesc => _blockDesc;
        /// <summary>
        /// input
        /// </summary>
        public string BlockType => _blockType;
        /// <summary>
        /// false
        /// </summary>
        public bool HasOccurs => _hasOccurs;

        /// <summary>
        /// id
        /// </summary>
        [XAQueryFieldAttribute("id")]
        public string id;

        public static class F
        {
            /// <summary>
            /// id
            /// </summary>
            public const string id = "id";
        }

        public static string[] AllFields = new string[]
        {
            F.id,
        };


        public override Dictionary<string, XAQueryFieldInfo> GetFieldsInfo()
        {
            Dictionary<string, XAQueryFieldInfo> dict = new Dictionary<string, XAQueryFieldInfo>();
            dict["id"] = new XAQueryFieldInfo("char", id, id, "id", (decimal)8);

            return dict;
        }

        public override void SetFieldValue(string fieldName, XAQueryFieldInfo fieldInfo)
        {
            switch (fieldName)
            {
                case "id":
                    this.id = fieldInfo.FieldValue.TrimEnd('?');
                break;


            }
        }

        public bool VerifyData()
        {
            if (id?.Length > 8) return false; // char 8

            return true;
        }
    }

    public partial class XQt0167OutBlock : XingBlock
    {
        /// <summary>
        /// t0167OutBlock
        /// </summary>
        public const string _blockName = "t0167OutBlock";
        /// <summary>
        /// 출력
        /// </summary>
        public const string _blockDesc = "출력";
        /// <summary>
        /// output
        /// </summary>
        public const string _blockType = "output";
        /// <summary>
        /// false
        /// </summary>
        public const bool _hasOccurs = false;
        /// <summary>
        /// t0167OutBlock
        /// </summary>
        public override string GetBlockName() => _blockName;
        /// <summary>
        /// t0167OutBlock
        /// </summary>
        public static string BlockName => _blockName;
        /// <summary>
        /// 출력
        /// </summary>
        public string BlockDesc => _blockDesc;
        /// <summary>
        /// output
        /// </summary>
        public string BlockType => _blockType;
        /// <summary>
        /// false
        /// </summary>
        public bool HasOccurs => _hasOccurs;

        /// <summary>
        /// 일자(YYYYMMDD)
        /// </summary>
        [XAQueryFieldAttribute("일자(YYYYMMDD)")]
        public string dt;
        /// <summary>
        /// 시간(HHMMSSssssss)
        /// </summary>
        [XAQueryFieldAttribute("시간(HHMMSSssssss)")]
        public string time;

        public static class F
        {
            /// <summary>
            /// 일자(YYYYMMDD)
            /// </summary>
            public const string dt = "dt";
            /// <summary>
            /// 시간(HHMMSSssssss)
            /// </summary>
            public const string time = "time";
        }

        public static string[] AllFields = new string[]
        {
            F.dt,
            F.time,
        };


        public override Dictionary<string, XAQueryFieldInfo> GetFieldsInfo()
        {
            Dictionary<string, XAQueryFieldInfo> dict = new Dictionary<string, XAQueryFieldInfo>();
            dict["dt"] = new XAQueryFieldInfo("char", dt, dt, "일자(YYYYMMDD)", (decimal)8);
            dict["time"] = new XAQueryFieldInfo("char", time, time, "시간(HHMMSSssssss)", (decimal)12);

            return dict;
        }

        public override void SetFieldValue(string fieldName, XAQueryFieldInfo fieldInfo)
        {
            switch (fieldName)
            {
                case "dt":
                    this.dt = fieldInfo.FieldValue.TrimEnd('?');
                break;

                case "time":
                    this.time = fieldInfo.FieldValue.TrimEnd('?');
                break;


            }
        }

        public static XQt0167OutBlock FromQuery(XQt0167 query)
        {
            XQt0167OutBlock block = new XQt0167OutBlock();
            block.IsValidData = true;
            block.InvalidReason = "";
            if (query.QueryResult != null && query.QueryResult.IsSystemError == true)
            {
                block.IsValidData = false;
                block.InvalidReason = query.ReceiveMessage;
                return block;
            }
            try
            {
                block.dt = query.GetFieldData(block.GetBlockName(), "dt", 0).TrimEnd('?'); // char 8
                block.time = query.GetFieldData(block.GetBlockName(), "time", 0).TrimEnd('?'); // char 12

            } catch (InvalidDataFormatException e) {
                block.IsValidData = false;
                block.InvalidReason = $"FieldName == {e.DataFieldName}, FieldData == \"{e.DataValue}\"";
            }
            return block;

        }

        public bool VerifyData()
        {
            if (dt?.Length > 8) return false; // char 8
            if (time?.Length > 12) return false; // char 12

            return true;
        }
    }

    /// <summary>
    /// 서버시간조회(t0167)
    /// </summary>
    public partial class XQt0167 : XingQuery
    {
        /// <summary>
        /// t0167
        /// </summary>
        public const string _typeName = "t0167";
        /// <summary>
        /// 서버시간조회(t0167)
        /// </summary>
        public const string _typeDesc = "서버시간조회(t0167)";
        /// <summary>
        /// 
        /// </summary>
        public const string _service = "";
        /// <summary>
        /// A
        /// </summary>
        public const string _headType = "A";
        /// <summary>
        /// 
        /// </summary>
        public const string _creator = "";
        /// <summary>
        /// 
        /// </summary>
        public const string _createdDate = "";
        /// <summary>
        /// false
        /// </summary>
        public const bool _attr = false;
        /// <summary>
        /// true
        /// </summary>
        public const bool _block = true;
        /// <summary>
        /// false
        /// </summary>
        public const bool _encrypt = false;
        /// <summary>
        /// false
        /// </summary>
        public const bool _signature = false;

        /// <summary>
        /// t0167
        /// </summary>
        public string TypeName => _typeName;
        /// <summary>
        /// 서버시간조회(t0167)
        /// </summary>
        public string TypeDesc => _typeDesc;
        /// <summary>
        /// 
        /// </summary>
        public string Service => _service;
        /// <summary>
        /// A
        /// </summary>
        public string HeadType => _headType;
        /// <summary>
        /// 
        /// </summary>
        public string Creator => _creator;
        /// <summary>
        /// 
        /// </summary>
        public string CreatedDate => _createdDate;
        /// <summary>
        /// false
        /// </summary>
        public bool Attr => _attr;
        /// <summary>
        /// true
        /// </summary>
        public bool Block => _block;
        /// <summary>
        /// false
        /// </summary>
        public bool Encrypt => _encrypt;
        /// <summary>
        /// false
        /// </summary>
        public bool Signature => _signature;

        public XQt0167() : base("t0167") { }


        public static XQt0167OutBlock Get(string id = default)
        {
            using (XQt0167 instance = new XQt0167())
            {
                instance.SetFieldData(XQt0167InBlock.BlockName, XQt0167InBlock.F.id, 0, id); // char 8

                if (instance.Request() < 0)
                {
                    return null;
                }

                var outBlock = instance.GetBlock();
                if (outBlock.IsValidData == false)
                {
                    return null;
                }
                return outBlock;
            }
        }

        public bool SetBlock(XQt0167InBlock block)
        {
            if (block.VerifyData() == false)
            {
                return false; // throw new ApplicationException("Failed to verify: " + block.BlockName);
            }

            _xaQuery.SetFieldData(block.GetBlockName(), "id", 0, block.id); // char 8

            return true;
        }

        public XQt0167OutBlock GetBlock()
        {
            XQt0167OutBlock instance = XQt0167OutBlock.FromQuery(this);
            return instance;

        }


    }

}

그렇기 때문에 여러분들은 하드 코딩할 필요 없이 다음과 같은 식으로 In/Out 데이터를 다룰 수 있습니다.

// XQt0167OutBlock outBlock = XQt0167.Get();

var outBlock = XQt0167.Get();
Console.WriteLine("dt == " + outBlock.dt);
Console.WriteLine("time == " + outBlock.time);

심지어 해당 Block의 필드 자체도 클래스의 필드로 추가해 두었기 때문에 다음과 같이 하드 코딩을 없앨 수 있습니다. (제가 인텔리센스 광팬입니다. ^^)

Console.WriteLine(XQt0167OutBlock.F.dt + " == " + outBlock.dt);
Console.WriteLine(XQt0167OutBlock.F.time + " == " + outBlock.time);

취향에 따라, Get 정적 메서드를 사용하지 않고 이를 풀어서 다음과 같이 사용하는 것도 가능합니다. (업데이트 2021-02-24: 현재 Get 버전의 내부에서 사용하는 XAQuery COM 개체에 메모리 누수가 있으므로 하루 종일 켜두면서 초당 몇 번씩 호출하는 식의 응용 프로그램에서는 사용하지 않는 것이 권장됩니다. 따라서 그런 경우에는 아래의 방법으로 query 인스턴스를 하나 생성해 두고 Request 호출을 반복적으로 처리하는 것이 좋습니다.)

using (XQt0167 query = new XQt0167())
{
    query.SetFieldData(XQt0167InBlock.BlockName, XQt0167InBlock.F.id, 0, "");
    if (query.Request() < 0)
    {
        Console.WriteLine("Failed to send request");
    }

    XQt0167OutBlock outBlock = query.GetBlock();
    if (outBlock.IsValidData == true)
    {
        outBlock.Dump(Console.Out, DumpOutputType.FormattedKeyValue);
    }
    else
    {
        Console.WriteLine($"Invalid: {outBlock.InvalidReason}");
    }
}

이 정도면... 좀 쓸만하겠죠? ^^

(첨부 파일은 t0167 예제 코드 및 app.manifest까지 구성한 완전한 예제 프로젝트입니다.)




유튜브에 보니까, python으로 xingAPI를 강의하는 것이 있습니다. 일단 21강 정도까지는,

[21강] xingAPI를 이용한 데이타 수신 : 종목코드조회(t8430) 
; https://www.youtube.com/watch?v=loO2isn8OXk

제가 만든 C# API로 무리 없이 따라 할 수 있었습니다. 말인즉, XingAPINet 라이브러리가 완벽하지 않을 있다는 점을 감안하시고 혹시 개선해야 할 점이 있다면 소스 코드가 모두 공개되었으니,

stjeong / XingAPI
; https://github.com/stjeong/XingAPI

PR을 날리셔도 좋겠고, "Issues" 게시판을 이용해 개선점을 남기시면 최대한 시간 날 때 반영해 보겠습니다. 물론, 저도 틈나는 대로 위에서 소개한 Youtube 강의를 보면서 그에 대응하는 예제를 직접 C#으로 만들어 추가하고 있습니다. 그래서 현재 다음의 경로에 보면,

XingAPI/DevCenterSample/TR/업종
; https://github.com/stjeong/XingAPI/tree/master/DevCenterSample/TR/%EC%97%85%EC%A2%85

t0167, t1475, t1511,... 등의 코드가 들어있으니 이를 참고해 코딩하셔도 무방합니다.




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

[연관 글]


donaricano-btn



[최초 등록일: ]
[최종 수정일: 2/24/2021]

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

비밀번호

댓글 쓴 사람
 



2020-07-17 10시56분
[나그네] 사용자 개체가 꾸준히 증가하네요. 사용자 개체가 많다는 오류를 만들고 죽는데요.
dispose가 안되는것이지.. ^^
[손님]
2020-07-17 01시16분
[나그네] XQt2101 같은 현재가를 초당 5번 가져오는것들은.. 내부적으로 dispose에
Marshal.ReleaseComObject(_xaQuery);
이 잘 선언되어 있음에도.. 해제가 안되고.. 사용자 개체가 늘어나는군요.
C#의 문제가 아니라 COM의 문제라고 보입니다. 어떻게 호출을 해도.. GC를 호출를 해도 해제가 안되네요.
[손님]
2021-02-03 03시20분
[하얀고래] ReleaseComObject 이슈가 아직도 해결될 기미가 없나 보군요.
C#으로 개발을 포기했던 이슈 인데~ 아쉽네요.
[손님]
2021-02-04 07시56분
@하얀고래 ReleaseComObject 이슈라는 게 뭔가요? 약간의 착오가 있는 것 같은데, 사실 COM의 구조는 매우 단순합니다. AddRef로 참조 카운트를 +1시키고, Release로 -1 시키는데, 그러다 0이 되면 스스로 사용하고 있는 개체를 삭제하는 것입니다. 즉, ReleaseComObject의 역할도 매우 단순해서 대상 COM 개체의 Ref 카운트만 -1 시키는 IUnknown::Release 함수만 실행하는 것입니다.

ReleaseComObject 관련한 대부분의 메모리 누수는 사용 측에서 QI(Queryinteface)된 것을 몰라 제대로 해제를 하지 않아서 그런 것입니다. 관련해서는 다음의 글을 참고하세요.

C# - Excel(을 비롯해 Office 제품군) COM 객체를 제어 후 Excel.exe 프로세스가 남아 있는 문제
; https://www.sysnet.pe.kr/2/0/11997

대개의 경우, 위와 같이 개발자가 잘못 사용하거 있거나, 아니면 (대상 ref count가 0이어서 삭제가 되는 그) COM 인스턴스 자체에서 적절한 메모리 해제를 하지 않은 경우입니다.
정성태
2021-02-04 08시08분
@나그네 답변이 매우 늦었는데, 제가 지금은 해당 이슈에 ^^; 관심이 식어서 더 이상 보고 있지는 않지만, 만약 그 문제가 발생한다면 다른 언어로 사용하는 것에도 마찬가지의 이슈가 발생할 것입니다. 단지, 이게 크게 부각되지 않는 이유라면 아마도 매번 XQt1102 생성으로 인한 XAQuery 개체를 생성시키기 보다는 한번 생성해 두고 Request 메서드만 반복적으로 호출하는 식이어서 그럴 수 있습니다.

따라서, 나그네님의 문제는 (물론 이상적으로는 그렇게 해도 문제가 없어야겠지만) 코드 사용 자체도 비효율적인 방식이어서 그렇게 사용해야 할 이유도 전혀 없으므로 약간만 변경하면 그냥 해결(?)될 수 있는 현상입니다.
정성태

... 16  17  18  19  20  21  22  23  24  25  26  27  28  [29]  30  ...
NoWriterDateCnt.TitleFile(s)
12154정성태2/25/20201957오류 유형: 593. warning LNK4070: /OUT:... directive in .EXP differs from output filename
12153정성태2/23/20202543.NET Framework: 898. Trampoline을 이용한 후킹의 한계파일 다운로드1
12152정성태2/23/20202267.NET Framework: 897. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Trampoline 후킹)파일 다운로드1
12151정성태2/22/20202661.NET Framework: 896. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 - 두 번째 이야기 (원본 함수 호출)파일 다운로드1
12150정성태2/21/20202490.NET Framework: 895. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 [1]파일 다운로드1
12149정성태2/20/20202530.NET Framework: 894. eBEST C# XingAPI 래퍼 - 연속 조회 처리 방법 [1]
12148정성태2/19/20202864디버깅 기술: 163. x64 환경에서 구현하는 다양한 Trampoline 기법
12147정성태2/19/20202492디버깅 기술: 162. x86/x64의 기계어 코드 최대 길이
12146정성태2/18/20202465.NET Framework: 893. eBEST C# XingAPI 래퍼 - 로그인 처리파일 다운로드1
12145정성태2/18/20202213.NET Framework: 892. eBEST C# XingAPI 래퍼 - Sqlite 지원 추가파일 다운로드1
12144정성태2/13/20202244.NET Framework: 891. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 두 번째 이야기파일 다운로드1
12143정성태2/13/20201911.NET Framework: 890. 상황별 GetFunctionPointer 반환값 정리 - x64파일 다운로드1
12142정성태2/12/20202489.NET Framework: 889. C# 코드로 접근하는 MethodDesc, MethodTable파일 다운로드1
12141정성태2/10/20202565.NET Framework: 888. C# - ASP.NET Core 웹 응용 프로그램의 출력 가로채기 [2]파일 다운로드1
12140정성태2/10/20202217.NET Framework: 887. C# - ASP.NET 웹 응용 프로그램의 출력 가로채기파일 다운로드1
12139정성태2/9/20202787.NET Framework: 886. C# - Console 응용 프로그램에서 UI 스레드 구현 방법
12138정성태2/9/20203273.NET Framework: 885. C# - 닷넷 응용 프로그램에서 Sqlite 사용 [3]파일 다운로드1
12137정성태2/9/20201998오류 유형: 592. [AhnLab] 경고 - 디버거 실행을 탐지했습니다.
12136정성태2/6/20201973Windows: 168. Windows + S(또는 Q)로 뜨는 작업 표시줄의 검색 바가 동작하지 않는 경우
12135정성태2/6/20203104개발 환경 구성: 468. Nuget 패키지의 로컬 보관 폴더를 옮기는 방법 [2]
12134정성태2/5/20204046.NET Framework: 884. eBEST XingAPI의 C# 래퍼 버전 - XingAPINet Nuget 패키지 [5]파일 다운로드1
12133정성태2/5/20202204디버깅 기술: 161. Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기
12132정성태1/28/20202968.NET Framework: 883. C#으로 구현하는 Win32 API 후킹(예: Sleep 호출 가로채기)파일 다운로드1
12131정성태1/27/20203278개발 환경 구성: 467. LocaleEmulator를 이용해 유니코드를 지원하지 않는(한글이 깨지는) 프로그램을 실행하는 방법 [1]
12130정성태1/26/20202137VS.NET IDE: 142. Visual Studio에서 windbg의 "Open Executable..."처럼 EXE를 직접 열어 디버깅을 시작하는 방법
12129정성태1/26/20204990.NET Framework: 882. C# - 키움 Open API+ 사용 시 Registry 등록 없이 KHOpenAPI.ocx 사용하는 방법 [1]
... 16  17  18  19  20  21  22  23  24  25  26  27  28  [29]  30  ...