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

(시리즈 글이 6개 있습니다.)
VS.NET IDE: 77. Visual Studio 확장(VSIX) 만드는 방법
; https://www.sysnet.pe.kr/2/0/1515

VS.NET IDE: 78. Visual Studio 확장으로 XmlCodeGenerator 제작하는 방법
; https://www.sysnet.pe.kr/2/0/1518

VS.NET IDE: 115. Visual Studio 확장(VSIX)을 이용해 사용자 메뉴 추가하는 방법
; https://www.sysnet.pe.kr/2/0/11184

VS.NET IDE: 116. Visual Studio 확장(VSIX)을 이용해 사용자 메뉴 추가하는 방법 (2) - 동적 메뉴 구성
; https://www.sysnet.pe.kr/2/0/11185

VS.NET IDE: 117. Visual Studio 확장(VSIX)을 이용해 사용자 매크로를 추가하는 방법
; https://www.sysnet.pe.kr/2/0/11186

VS.NET IDE: 165. Visual Studio 2022를 위한 Extension 마이그레이션
; https://www.sysnet.pe.kr/2/0/12682




Visual Studio 확장(VSIX)을 이용해 사용자 메뉴 추가하는 방법 (2) - 동적 메뉴 구성

지난 글에서 메뉴 추가 방법에 대한 기본적인 내용을 살펴봤습니다.

Visual Studio 확장(VSIX)을 이용해 사용자 메뉴 추가하는 방법
; https://www.sysnet.pe.kr/2/0/11184

그런데, 때로는 메뉴를 동적으로 구성해야 할 때가 있습니다. Visual Studio의 경우 대표적인 사례로 "File" / "Recent Files" 하위 메뉴를 들 수 있는데, 이를 위한 방법 역시 문서화가 너무 잘되어 있습니다. ^^

Dynamically Adding Menu Items
; https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/extensibility/dynamically-adding-menu-items

위의 글에 따라, 동적으로 추가되는 "매크로 명령어 목록"을 구성해 보겠습니다.

우선, 동적 메뉴로 펼쳐질 서브 메뉴의 부모가 되어줄 Menu가 있어야 하는데, 그 메뉴는 또한 Command 그룹에 속해 있어야 합니다. 기본적으로 .vsct 파일에는 ("Extensibility" / "Custom Command"를 추가한 경우) 한 개의 그룹이 등록되어 있으므로,

<Groups>
    <Group guid="guidMacroCommandPackageCmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
    </Group>
</Groups>

위의 "MyMenuGroup"과 연결될 메뉴만 하나 추가해 주면 됩니다.

<Menus>
    <Menu guid="guidMacroCommandPackageCmdSet" id="MyMenuController" priority="0x1000" type="MenuController">
	<Parent guid="guidMacroCommandPackageCmdSet" id="MyMenuGroup" />
	<CommandFlag>DynamicVisibility</CommandFlag>
	<Strings>
	    <ButtonText>MyMacro List</ButtonText>
	</Strings>
    </Menu>
</Menus>

위의 상태까지만 보면, "Tools" 메뉴에 "MyMacro List"라는 이름의 메뉴가 생성되도록 정의되는데 그 메뉴의 타입이 "MenuController" 유형입니다. (이 유형이어야만 자유롭게 자신의 서브 메뉴를 정의할 수 있는 것 같습니다.)

그다음, 서브 메뉴를 담을 그룹을 하나 정의합니다.

<Groups>
    <Group guid="guidMacroCommandPackageCmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
    </Group>

    <Group guid="guidMacroCommandPackageCmdSet" id="MySubMenuGroup" priority="0x0600">
        <Parent guid="guidMacroCommandPackageCmdSet" id="MyMenuController" />
    </Group>
</Groups>

당연히 이 서브 메뉴 그룹의 부모는 "Tools" / "MyMacro List" 메뉴가 되어야 하므로 "<Parent />"를 통해 메뉴의 id인 "MyMenuController"를 부모로 지정해 준 것입니다. 그리고 이 서브 메뉴(예제에서는 매크로 명령어들)가 나열되기 위한 기본 버튼(이라 쓰고 메뉴라고 읽음) 하나를 지정해 줍니다.

<Buttons>
    <Button guid="guidMacroCommandPackageCmdSet" id="cmdidMyDynamicStartCommand" priority="0x0100">
    <Parent guid="guidMacroCommandPackageCmdSet" id="MySubMenuGroup" />
    <CommandFlag>DynamicItemStart</CommandFlag>
    <CommandFlag>DynamicVisibility</CommandFlag>
    <CommandFlag>TextChanges</CommandFlag>
    <Strings>
        <ButtonText>MacroSubList</ButtonText>
    </Strings>
    </Button>
</Buttons>

마지막으로, 지금까지 설정했던 노드마다 사용되었던 ID에 대한 심벌 등록을 해줍니다.

<GuidSymbol name="guidMacroCommandPackageCmdSet" value="{6d15ea04-448b-4c65-b70c-cbfaa3c105b7}">
    <IDSymbol name="MyMenuGroup" value="0x1000" />
    <IDSymbol name="MySubMenuGroup" value="0x1500" />
    <IDSymbol name="MyMenuController" value ="0x2000"/>
    <IDSymbol name="cmdidMyDynamicStartCommand" value="0x3002" />
</GuidSymbol>

일단, 여기까지의 .vsct 파일 변경과 함께 지난번의 소스 코드에서 MacroCommand의 생성자를 다음과 같이 아무 일도 안 하는 코드로 변경해서 실행하면,

private MacroCommand(Package package)
{
    if (package == null)
    {
        throw new ArgumentNullException("package");
    }
            
    this.package = package;
}

"Tools" 메뉴에 "MyMacro List" 메뉴가 포함되고, 그 하위로 "MacroSubList" 메뉴가 뜹니다.

dynamic_menu_in_vsix_1.png




우리가 원하는 것은, 바로 "MacroSubList"가 나와야 되는 단계의 메뉴를 동적으로 구성하는 것입니다. 이를 위해 MacroCommand의 생성자를 다음과 같이 변경해 줍니다.

public const int CommandId = 0x3002;  // .vsct에서 지정한 cmdidMyDynamicStartCommand 심벌 값
private DTE2 dte2;

private MacroCommand(Package package)
{
    if (package == null)
    {
        throw new ArgumentNullException("package");
    }

    _repo = new MacroRepository(); // 이 클래스 코드는 나중에 설명을 합니다.
            
    this.package = package;

    OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
    if (commandService != null)
    {
        CommandID dynamicItemRootId = new CommandID(CommandSet, (int)CommandId);
        DynamicItemMenuCommand dynamicMenuCommand = new DynamicItemMenuCommand(dynamicItemRootId,
            _repo,
            OnInvokedDynamicItem,
            OnBeforeQueryStatusDynamicItem);

        commandService.AddCommand(dynamicMenuCommand);
    }

    dte2 = (DTE2)this.ServiceProvider.GetService(typeof(DTE));
}

가장 다른 점이 있다면 기존의 코드는 MenuCommand 인스턴스를 생성해 commandService.AddCommand에 건네주었던 반면, 이번에는 별도로 정의한 DynamicItemMenuCommand 타입을 생성해서 전달하고 있습니다. 이 타입은 OleMenuCommand를 상속받은 것으로 다음과 같이 코드를 추가하면 됩니다.

using System;
using System.ComponentModel.Design;
using System.Globalization;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using EnvDTE80;

namespace VSMacro
{
    public class DynamicItemMenuCommand : OleMenuCommand
    {
        MacroRepository _repo;

        public DynamicItemMenuCommand(CommandID rootId, MacroRepository repo,
            EventHandler invokeHandler, EventHandler beforeQueryStatusHandler)
            : base(invokeHandler, null /*changeHandler*/, beforeQueryStatusHandler, rootId)
        {
            _repo = repo;
        }

        public override bool DynamicItemMatch(int cmdId)
        {
            if (_repo.ContainsCmd(cmdId))
            {
                this.MatchedCommandId = cmdId;
                return true;
            }

            return false;
        }
    }
}

DynamicItemMenuCommand가 하는 것은 오로지 Visual Studio가 조회하는 Command ID가 올바른지에 대해서만 DynamicItemMatch 가상 함수를 통해 판단해 주는 것입니다.

여기서부터, 구현이 좀 복잡하고 이해가 다소 직관적이지 않은 부분이 나옵니다. 일단, Visual Studio는 (OleMenuCommand를 상속받은) 메뉴가 있는 경우, 그 메뉴의 생성자에 전달된 마지막 인자(OnBeforeQueryStatusDynamicItem)였던 delegate를 호출합니다. 즉, 다음의 코드가 실행됩니다.

private void OnBeforeQueryStatusDynamicItem(object sender, EventArgs args)
{
    DynamicItemMenuCommand matchedCommand = (DynamicItemMenuCommand)sender;

    matchedCommand.Enabled = true;
    matchedCommand.Visible = true;

    // matchedCommand.MatchedCommandId
}

그런데, 이때 실행되는 matchedCommand의 MatchedCommandId가 수상합니다. 예상으로는 생성자 호출 시에 넘겨줬던 CommandID를 따르는 CommandId(0x3002)여야 할 텐데, Visual Studio는 ("MacroSubList"로 이미 하나 점유하고 있던) 첫 번째 메뉴에 대해 matchedCommand.MatchedCommandId == 0으로 설정해서 호출합니다. 따라서, 동적 메뉴의 첫 번째 항목을 구분하는 Command ID는 0이 되고, 그에 따라 텍스트를 다음과 같은 식으로 변경해 줘야 합니다.

private void OnBeforeQueryStatusDynamicItem(object sender, EventArgs args)
{
    DynamicItemMenuCommand matchedCommand = (DynamicItemMenuCommand)sender;

    matchedCommand.Enabled = true;
    matchedCommand.Visible = true;

    if (matchedCommand.MatchedCommandId == 0)
    {
        matchedCommand.Text = "CMD1";
    }
}

즉, 동적 메뉴라고는 하지만 최소 1개는 이미 포함하고 있는 상태의 메뉴를 구성해야 하는 것입니다.

그다음, Visual Studio는 추가로 메뉴가 있는지에 대해 알아내야 하는데 이를 위해 (OleMenuCommand를 상속받은) DynamicItemMenuCommand로 cmdidMyDynamicStartCommand(0x3002) 값에 +1을 하면서 그 값이 유효한 지에 대해 OleMenuCommand.DynamicItemMatch 메서드를 통해 묻습니다. 따라서 표시해야 할 동적 메뉴가 2개 더 있다면 다음과 같이 구현해 줘야 합니다.

public override bool DynamicItemMatch(int cmdId)
{
    if (cmdId == 0)
    {
        return true; // 가장 첫 번째 메뉴 == 유효
    }

    if (cmdId == (MacroCommand.CommandId + 1))
    {
        this.MatchedCommandId = cmdId;
        return true;  // 두 번째 메뉴 == 유효
    }

    if (cmdId == (MacroCommand.CommandId + 2))
    {
        this.MatchedCommandId = cmdId;
        return true; // 세 번째 메뉴 == 유효
    }

    return false; // 이외의 메뉴 ID들은 유효하지 않음.
}

이쯤에서, 위와 같이 구성하는 것이 다소 번거로우니 동적 메뉴를 위한 구성을 담을 MacroRepository 코드를 다음과 같이 작성합니다.

using System.Collections.Generic;

namespace VSMacro
{
    public class MacroRepository
    {
        List<MacroItem> _macros = new List<MacroItem>();

        public MacroRepository()
        {
            _macros.Add(new MacroItem(0, "CMD1", "__cmd1__"));
            _macros.Add(new MacroItem(MacroCommand.CommandId + 1, "CMD2", "__cmd2__"));
            _macros.Add(new MacroItem(MacroCommand.CommandId + 2, "CMD3", "__cmd3__"));
        }

        public string GetCommandTextByPos(int index)
        {
            if (index != 0)
            {
                index = index - MacroCommand.CommandId;
            }

            return _macros[index].Text;
        }

        public string GetCommandSourceCodeByPos(int index)
        {
            if (index != 0)
            {
                index = index - MacroCommand.CommandId;
            }

            return _macros[index].SourceCode;
        }

        public bool ContainsCmd(int cmdId)
        {
            return _macros.Exists((item) => item.Id == cmdId);
        }
    }

    public class MacroItem
    {
        int _id;
        public int Id
        {
            get { return _id; }
        }

        string _text;
        public string Text
        {
            get { return _text; }
        }

        string _sourceCode;
        public string SourceCode
        {
            get { return _sourceCode; }
        }

        public MacroItem()
        {
        }

        public MacroItem(int id, string text, string sourceCode)
        {
            _id = id;
            _text = text;
            _sourceCode = sourceCode;
        }
    }
}

그럼, DynamicItemMenuCommand.DynamicItemMatch 코드를 다음과 같이 변경할 수 있고,

public override bool DynamicItemMatch(int cmdId)
{
    if (_repo.ContainsCmd(cmdId))
    {
        this.MatchedCommandId = cmdId; // 이때 설정한 MatchedCommandId 값 덕분에,
                                        // 연이어 호출되는 OnBeforeQueryStatusDynamicItem 메서드에서
                                        // 방금 전에 Visual Studio가 조회한 동적 메뉴의 ID가 무엇인지 알 수 있게 됨.
        return true;
    }

    return false;
}

MacroCommand.OnBeforeQueryStatusDynamicItem 메서드는 이렇게 정의할 수 있습니다.

private void OnBeforeQueryStatusDynamicItem(object sender, EventArgs args)
{
    DynamicItemMenuCommand matchedCommand = (DynamicItemMenuCommand)sender;

    matchedCommand.Enabled = true;
    matchedCommand.Visible = true;

    matchedCommand.Text = _repo.GetCommandTextByPos(matchedCommand.MatchedCommandId);

    matchedCommand.MatchedCommandId = 0;
}

위의 코드들을 보시면, 좀 이해가 안 되는 부분이 있습니다. 전통적인 생각으로는, 동적 메뉴를 구성한다면 Menu 객체가 하나 있고 그것의 Children 속성에 개별 메뉴에 해당하는 Menu 인스턴스들이 생성될 것으로 예상하게 됩니다. 가령 다음과 같은 코드를 상상할 수 있습니다.

// Visual Studio 측 (가상 코드)
Menu root = new Menu("MyMacro List");
MenuExtension_AddSubMenu(root);

// 확장 모듈 측
MenuExtension_AddSubMenu(root)
{
    root.Children.Add(new Menu("CMD1"));
    root.Children.Add(new Menu("CMD2"));
    root.Children.Add(new Menu("CMD3"));
}

그런데, Visual Studio는 다음과 같은 식으로 동적 메뉴를 구성하고 있습니다.

// Visual Studio 측 (가상 코드)
Menu root = new Menu("MyMacro List");
Menu subMenu = new Menu("MacroSubList");

OleMenuCommand dynamicMenu = new ExtensionMenu(subMenu);

dynamicMenu.MatchedCommandId = 0;
dynamicMenu.OnBeforeQueryStatusDynamicItem(dynamicMenu, EventArgs.Empty); // 이때 subMenu.Text 속성에 "CMD1"을 설정해야 함.

int startId = dynamicMenu.MatchedCommandId + 1;
while (true)
{
    dynamicMenu.MatchedCommandId = startId;

    if (dynamicMenu.DynamicItemMatch(startId) == false)
    {
        break;
    }

    dynamicMenu.OnBeforeQueryStatusDynamicItem(dynamicMenu, EventArgs.Empty); // 이때 2번째 이후의 동적 메뉴의 문자열을 설정
    startId ++;
}

가만 보면, OleMenuCommand 인스턴스 하나로 동적으로 추가해야 할 모든 메뉴를 처리하고 있는 것입니다. 즉, OnBeforeQueryStatusDynamicItem이나 DynamicItemMatch가 호출될 때의 메뉴 인스턴스는 모두 동일하게/단일한 DynamicItemMenuCommand 인스턴스가 되는 것입니다. (그렇습니다, 처리 절차가 일반적이지 않아서 좀 혼란스럽습니다.)

암튼 저렇게 해서 결국 동적 메뉴로 구성된 항목을 사용자가 선택하면 DynamicItemMenuCommand의 3번째 인자로 넘겨준 OnInvokedDynamicItem 메서드를 호출해 줍니다.

private void OnInvokedDynamicItem(object sender, EventArgs args)  
{  
    DynamicItemMenuCommand invokedCommand = (DynamicItemMenuCommand)sender;  

    // invokedCommand.MatchedCommandId
    // invokedCommand.Text
}  

재미있는 것은, 이 메서드가 호출될 때의 invokedCommand.MatchedCommandId는 해당 메뉴의 존재를 확인하기 위해 DynamicItemMatch를 통해 주고받았던 ID라고 장담할 수 없습니다. 왜냐하면 결국 동적 메뉴를 의미하는 DynamicItemMenuCommand 객체는 1개이기 때문에 그 인스턴스에 현재 어떤 값을 설정하고 있느냐에 따라 달라질 수 있기 때문입니다. 즉, 개발자가 작성한 코드가 어떤 식이냐에 따라 MatchedCommandId가 선택된 메뉴의 ID가 맞을 수도/맞지 않을 수도 있습니다.

사실 Visual Studio는 선택된 동적 메뉴의 MatchedCommandId를 보관하고 있지 않는 것처럼(실제로 보관하고 있지 않는 듯) 행동합니다. 그래서, 첫 번째 메뉴가 선택되었을 때 Visual Studio는 OnInvokedDynamicItem을 다음과 같은 문맥으로 호출해 줍니다.

// Visual Studio 측 (가상 코드)

OleMenuCommand dynamicMenu = ...;

dynamicMenu.MatchedCommandId = 0;
dynamicMenu.OnBeforeQueryStatusDynamicItem(dynamicMenu, EventArgs.Empty);
dynamicMenu.OnInvokedDynamicItem(dynamicMenu, EventArgs.Empty);

즉, 첫 번째 메뉴이기 때문에 MatchedCommandId를 0으로 설정하고 OnBeforeQueryStatusDynamicItem 호출로 상태를 확인한 다음 OnInvokedDynamicItem을 호출하는 것입니다.

동적으로 추가된 두 번째 메뉴부터는 말 그대로 .vsct에 등록했던 cmdidMyDynamicStartCommand(0x3002)의 값에 "+ N"을 하면서 이렇게 호출합니다.

// Visual Studio 측 (가상 코드)
OleMenuCommand dynamicMenu = ...;

// 2번째 메뉴가 선택되었다면,
dynamicMenu.DynamicItemMatch(cmdidMyDynamicStartCommand + 1);           // 호출로 인해 dynamicMenu.MatchedCommandId == cmdidMyDynamicStartCommand + 1로 설정됨
dynamicMenu.OnBeforeQueryStatusDynamicItem(dynamicMenu, EventArgs.Empty); // 호출로 인해 dynamicMenu.MatchedCommandId == 0으로 설정됨
dynamicMenu.DynamicItemMatch(cmdidMyDynamicStartCommand + 1);           // 호출로 인해 다시 dynamicMenu.MatchedCommandId == cmdidMyDynamicStartCommand + 1로 설정됨
dynamicMenu.OnInvokedDynamicItem(dynamicMenu, EventArgs.Empty);         // 결국 dynamicMenu.MatchedCommandId == cmdidMyDynamicStartCommand + 1 값으로 호출됨

// 3번째 메뉴가 선택되었다면,
dynamicMenu.DynamicItemMatch(cmdidMyDynamicStartCommand + 2);           // 호출로 인해 dynamicMenu.MatchedCommandId == cmdidMyDynamicStartCommand + 2로 설정됨
dynamicMenu.OnBeforeQueryStatusDynamicItem(dynamicMenu, EventArgs.Empty); // 호출로 인해 dynamicMenu.MatchedCommandId == 0으로 설정됨
dynamicMenu.DynamicItemMatch(cmdidMyDynamicStartCommand + 2);           // 호출로 인해 다시 dynamicMenu.MatchedCommandId == cmdidMyDynamicStartCommand + 1로 설정됨
dynamicMenu.OnInvokedDynamicItem(dynamicMenu, EventArgs.Empty);         // 결국 dynamicMenu.MatchedCommandId == cmdidMyDynamicStartCommand + 1 값으로 호출됨

저 상황을 보고, DynamicItemMatch, OnBeforeQueryStatusDynamicItem의 메서드 구현을 다시 보면 왜 MatchedCommandId의 값에 0이나 cmdId를 명시적으로 설정해 주는 코드가 들어가야 하는지 이해할 수 있습니다.




위와 같은 코드로 Visual Studio를 실행하면 다음과 같은 화면을 볼 수 있고,

dynamic_menu_in_vsix_2.png

각각의 메뉴를 선택하면, MacroItem 객체에 설정했던 SourceCode 속성의 문자열이 메시지 상자로 뜨는 것을 확인할 수 있습니다.

(이 글의 첨부 파일은 예제 코드를 포함합니다.)




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







[최초 등록일: ]
[최종 수정일: 7/13/2021]

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

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1780정성태10/15/201424153오류 유형: 249. The application-specific permission settings do not grant Local Activation permission for the COM Server application with CLSID
1779정성태10/15/201419659오류 유형: 248. Active Directory에서 OU가 지워지지 않는 경우
1778정성태10/10/201418099오류 유형: 247. The Netlogon service could not create server share C:\Windows\SYSVOL\sysvol\[도메인명]\SCRIPTS.
1777정성태10/10/201421207오류 유형: 246. The processing of Group Policy failed. Windows attempted to read the file \\[도메인]\sysvol\[도메인]\Policies\{...GUID...}\gpt.ini
1776정성태10/10/201418225오류 유형: 245. 이벤트 로그 - Name resolution for the name _ldap._tcp.dc._msdcs.[도메인명]. timed out after none of the configured DNS servers responded.
1775정성태10/9/201419369오류 유형: 244. Visual Studio 디버깅 (2) - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
1774정성태10/9/201426566개발 환경 구성: 246. IIS 작업자 프로세스의 20분 자동 재생(Recycle)을 끄는 방법
1773정성태10/8/201429750.NET Framework: 471. 웹 브라우저로 다운로드가 되는 파일을 왜 C# 코드로 하면 안되는 걸까요? [1]
1772정성태10/3/201418519.NET Framework: 470. C# 3.0의 기본 인자(default parameter)가 .NET 1.1/2.0에서도 실행될까? [3]
1771정성태10/2/201428037개발 환경 구성: 245. 실행된 프로세스(EXE)의 명령행 인자를 확인하고 싶다면 - Sysmon [4]
1770정성태10/2/201421664개발 환경 구성: 244. 매크로 정의를 이용해 파일 하나로 C++과 C#에서 공유하는 방법 [1]파일 다운로드1
1769정성태10/1/201424081개발 환경 구성: 243. Scala 개발 환경 구성(JVM, 닷넷) [1]
1768정성태10/1/201419491개발 환경 구성: 242. 배치 파일에서 Thread.Sleep 효과를 주는 방법 [5]
1767정성태10/1/201424606VS.NET IDE: 94. Visual Studio 2012/2013에서의 매크로 구현 - Visual Commander [2]
1766정성태10/1/201422422개발 환경 구성: 241. 책 "프로그래밍 클로저: Lisp"을 읽고 나서. [1]
1765정성태9/30/201425992.NET Framework: 469. Unity3d에서 transform을 변수에 할당해 사용하는 특별한 이유가 있을까요?
1764정성태9/30/201422254오류 유형: 243. 파일 삭제가 안 되는 경우 - The action can't be comleted because the file is open in System
1763정성태9/30/201423843.NET Framework: 468. PDB 파일을 연동해 소스 코드 라인 정보를 알아내는 방법파일 다운로드1
1762정성태9/30/201424538.NET Framework: 467. 닷넷에서 EIP/RIP 레지스터 값을 구하는 방법 [1]파일 다운로드1
1761정성태9/29/201421540.NET Framework: 466. 윈도우 운영체제의 보안 그룹 이름 및 설명 문자열을 바꾸는 방법파일 다운로드1
1760정성태9/28/201419804.NET Framework: 465. ICorProfilerInfo::GetILToNativeMapping 메서드가 0x80131358을 반환하는 경우
1759정성태9/27/201430962개발 환경 구성: 240. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법파일 다운로드1
1758정성태9/23/201437808개발 환경 구성: 239. 원격 데스크톱 접속(RDP)을 기존의 콘솔 모드처럼 사용하는 방법 [1]
1757정성태9/23/201418377오류 유형: 242. Lync로 모임 참여 시 소리만 들리지 않는 경우 - 두 번째 이야기
1756정성태9/23/201427374기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434174오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...