Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 7개 있습니다.)
.NET Framework: 167. 다른 스레드의 호출 스택 덤프 구하는 방법
; https://www.sysnet.pe.kr/2/0/802

.NET Framework: 260. .NET 스레드 콜 스택 덤프 (2) - Managed Stack Explorer 소스 코드를 이용한 스택 덤프 구하는 방법
; https://www.sysnet.pe.kr/2/0/1162

.NET Framework: 261. .NET 스레드 콜 스택 덤프 (3) - MSE 소스 코드 개선
; https://www.sysnet.pe.kr/2/0/1163

.NET Framework: 262. .NET 스레드 콜 스택 덤프 (4) - .NET 4.0을 지원하지 않는 MSE 응용 프로그램 원인 분석
; https://www.sysnet.pe.kr/2/0/1164

.NET Framework: 311. .NET 스레드 콜 스택 덤프 (5) - ICorDebug 인터페이스 사용법
; https://www.sysnet.pe.kr/2/0/1249

.NET Framework: 392. .NET 스레드 콜 스택 덤프 (6) - MDbg를 이용한 방법
; https://www.sysnet.pe.kr/2/0/1534

.NET Framework: 606. .NET 스레드 콜 스택 덤프 (7) - ClrMD(Microsoft.Diagnostics.Runtime)를 이용한 방법
; https://www.sysnet.pe.kr/2/0/11043




.NET 스레드 콜 스택 덤프 (5) - ICorDebug 인터페이스 사용법

닷넷에서 스레드 호출 스택을 얻으려고 하는 시도를 하다가 어느새 여기까지 와버렸군요. ^^; 지난 마지막 이야기에서 "mscordbi.dll"을 직접 사용하는 방법을 다뤄보겠다고 하면서 맺었는데... 진작에 테스트를 했었으나, 시간이 없어 미뤄오다가 이제서야 정리를 해서 공개를 합니다.

자... 그럼 이번에도 그다지 재미없는 글이 되겠지만 ^^ 본론으로 들어가 보도록 하겠습니다.




문제를 다시 정리해 보면, 현재 MSE의 소스 코드로는 .NET 2.0 응용 프로그램의 Call Stack만을 얻을 수 있을 뿐 .NET 4.0의 콜 스택을 얻지 못한다는 단점이 있었습니다. 다행히 .NET 4.0의 콜 스택을 얻는 방법으로 그 힌트를 CLR Stack Explorer가 .NET 4.0 응용 프로그램에서도 동작한다는 것에서 얻었는데, 그래서 이번에는 CLR Stack Explorer처럼 직접 mscordbi.dll에서 제공되는 기능만으로 문제 해결을 해보려는 것입니다.

비록 MSE 응용 프로그램에서 예외는 ICorPublish에서 발생했지만, 사실 ICorPublish의 기능 자체는 콜 스택 덤프를 뜨는 것과는 무관합니다. 정작 중요한 것은 ICorDebug 인터페이스인데요.

이에 대한 간략한 사용법은 다음의 예제에 공개되어 있습니다.

How to get a V2.0 ICorDebug object
; http://blogs.msdn.com/b/jmstall/archive/2005/01/15/353717.aspx

ICorDebug re-architecture in CLR 4.0
; https://learn.microsoft.com/en-us/archive/blogs/rmbyers/icordebug-re-architecture-in-clr-4-0

처음 시도에서는 위의글에 설명된 CoCreateInstance로 직접 ICorDebug를 생성해 보았습니다.

#include <cor.h>
#include <corhdr.h>
#include <cordebug.h>

#pragma comment(lib, "corguids.lib")

    CoInitialize(NULL);

    {
        ICorDebug *pCorDebug;

        HRESULT hr = ::CoCreateInstance(CLSID_CorDebug, NULL, CLSCTX_INPROC_SERVER, IID_ICorDebug,
            (void **)&pCorDebug);
        if (hr != S_OK)
        {
            return 0;
        }
    }

    CoUninitialize();

문제가 대번에 나오더군요. Visual Studio의 출력창에서 .NET 2.0 용의 mscordbi.dll 파일이 로드가 되는 것을 확인할 수 있었습니다.

'ConsoleApp.exe': Loaded 'C:\Windows\SysWOW64\clbcatq.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\SysWOW64\mscoree.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscordbi.dll', Cannot find or open the PDB file

이전에 설명했던 것처럼 ICorDebug 등에 대한 Class Factory는,

EXTERN_C const IID LIBID_CORDBLib;

EXTERN_C const CLSID CLSID_CorDebug;

#ifdef __cplusplus

class DECLSPEC_UUID("6fef44d0-39e7-4c77-be8e-c9f8cf988630")
CorDebug;

mscoree.dll에 정의되어 있습니다. 결국 mscoree.dll 측에서 적절하지 않은 버전의 mscordbi.dll을 로드한다는 것인데요. 따라서, mscoree.dll을 경유하지 말고 직접 mscordbi.dll을 로드하는 방법을 찾아야 했습니다. 일단 mscoree.dll에서 CoClass를 제공하는 이상 mscordbi.dll 자체에서는 숨겨진 다른 방식으로 인터페이스를 노출시켜 준다는 의미가 되는데요. Depends 도구를 이용하여 mscordbi.dll을 살펴보니, 친절하게도 CreateCordbObject라는 이름으로 된 함수가 export 되어 있었습니다.

how_to_use_icordebug_1.png

다행히, API 설명까지 공개되어 있군요. ^^

CreateCordbObject Function
; https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/createcordbobject-function

종합해서, 다음과 같이 특정 버전의 mscordbi.dll 로드가 가능했습니다.

===== C/C++ 버전 =====

typedef HRESULT (__stdcall *CordbCreateObjectFunc)(int iDebuggerVersion, IUnknown **ppCordb);

CComQIPtr<ICorDebug> pCorDebug;
HMODULE hModule = ::LoadLibrary(L"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\mscordbi.dll");
if (hModule == NULL)
{
    break;
}

CordbCreateObjectFunc pCordbCreateObjectFunc = (CordbCreateObjectFunc)::GetProcAddress(hModule,"CreateCordbObject");

IUnknown *pUnk;
hr = pCordbCreateObjectFunc(CorDebugVersion_4_0, (IUnknown **)&pUnk);
if (hr != S_OK)
{
    break;
}

pCorDebug = pUnk;
if (pCorDebug == NULL)
{
    break;
}

===== C# 버전 =====
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr LoadLibrary(string lpFileName);

[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int CordbCreateObjectFunc(int iDebuggerVersion, [Out, MarshalAs(UnmanagedType.Interface)] out object pUnknown);

IntPtr pModule = LoadLibrary("C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\mscordbi.dll");
if (pModule == null)
{
    return;
}

IntPtr pFunc = GetProcAddress(pModule, "CreateCordbObject");
if (pFunc == IntPtr.Zero)
{
    return;
}

CordbCreateObjectFunc pCordbCreateObjectFunc = Marshal.GetDelegateForFunctionPointer(pFunc, typeof(CordbCreateObjectFunc))
    as CordbCreateObjectFunc;
if (pCordbCreateObjectFunc == null)
{
    return;
}

// object pUnknown;
pCordbCreateObjectFunc(4, out pUnknown);

ICorDebug corDebug = pUnknown as ICorDebug;

참고로, 현재 CorDebugVersion_4_0 상수값에 대해서는 어떠한 웹 문서에도 찾아볼 수가 없었는데, 최신 버전의 Windows SDK에 포함된 cordebug.idl 파일을 직접 검색해 보면 다음과 같이 정의되어 있는 것을 볼 수 있습니다.

E:\Program Files\Microsoft SDKs\Windows\v7.1\Include\cordebug.idl

    CorDebugInvalidVersion = 0,
    CorDebugVersion_1_0 = CorDebugInvalidVersion + 1,  // 1
    CorDebugVersion_1_1 = CorDebugVersion_1_0 + 1,     // 2
    CorDebugVersion_2_0 = CorDebugVersion_1_1 + 1,     // 3
    // CLR v4 - next major CLR version after CLR v2
    // Includes Silverlight 4
    CorDebugVersion_4_0 = CorDebugVersion_2_0 + 1,     // 4

실제로 이렇게 해서 로드를 해보면, 정확하게 4.0 버전의 mscordbi.dll만 Visual Studio 출력창에 보입니다.

'ConsoleApp.exe': Loaded 'C:\Windows\System32\version.dll', Cannot find or open the PDB file
IMGSF01-DLL_PROCESS_ATTACH'ConsoleApp.exe': Loaded 'C:\Windows\System32\ntmarta.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\System32\Wldap32.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\System32\msimg32.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscordbi.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\System32\wtsapi32.dll', Cannot find or open the PDB file

성공입니다. ^^




mscordbi.dll 파일 경로를 직접 쓴다는 것은 아무래도 부담스러운데요. 이 때문에 "How to get a V2.0 ICorDebug object" 글에서 설명한 CreateDebuggingInterfaceFromVersion 함수를 사용해 보았습니다.

CreateDebuggingInterfaceFromVersion Function
; https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/createdebugginginterfacefromversion-function
(.NET 4.0에서는 "obsolete"로 명시되었지만, 여전히 동작합니다.)

처음에는 다음과 같이 사용해 보았는데요.

CComPtr<ICorDebug> pCorDebug;
hr = ::CreateDebuggingInterfaceFromVersion(CorDebugVersion_4_0, L"", (IUnknown **)&pCorDebug);

아쉽게도 hr == E_INVALIDARG 오류가 발생해서, 문서를 자세히 보니 두 번째 szDebuggeeVersion 인자를 주어야 한다는 것을 알았습니다. 역시 문서에 보면 여기에 넘겨줄 문자열은 GetVersionFromProcessGetRequestedRuntimeVersion API에서 구할 수 있다고 되어 있는데, 제 상황에서는 GetVersionFromProcess를 실행해 보았으나 "" 문자열만 반환되었습니다. (참고로, 문서에 보면 .NET 4.0의 경우 GetVersionFromProcess API 역시 "deprecated" 되었습니다.)

// 아래의 코드는 .NET 4.0용에서 동작하지 않음.
wchar_t versionText[1024] = { 0 };
DWORD dwVersionLength = 0;
hr = GetVersionFromProcess((HANDLE)processHandle, versionText, 1024, &dwVersionLength);

// 그래서, .NET 4.0에 해당하는 문자열을 넣어주는 것으로 해결
hr = ::CreateDebuggingInterfaceFromVersion(CorDebugVersion_4_0, L"v4.0.30319", (IUnknown **)&pCorDebug);

==== C# 버전 ====
[DllImport("MSCorEE.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
static extern object CreateDebuggingInterfaceFromVersion(int iDebuggerVersion, string szDebuggerVersion);

ICorDebug corDebug = CreateDebuggingInterfaceFromVersion(4, "v4.0.30319") as ICorDebug;

어쨌든, Visual Studio의 출력창에 로드된 DLL을 확인해 보면, 정확하게 .NET 4.0용의 mscordbi.dll이 로드된 것을 확인할 수 있습니다.




ICorDebug를 얻었으니, 이로부터 다음과 같이 ICorDebugProcess 인터페이스를 얻어올 수 있습니다.

CComPtr<ICorDebugProcess> pCorDebugProcess;
hr = pCorDebug->DebugActiveProcess(dwPid, true, &pCorDebugProcess);
if (hr != S_OK)
{
    break;
}

하지만, 세상일이 그렇게 간단하지 않더군요. ^^ 실제로 위와 같이 호출해 보면 다음과 같은 예외가 발생합니다.

hr == E_FAIL

First-chance exception at 0x000007fefe18cacd in ConsoleApp.exe: Microsoft C++ exception: HRException * __ptr64 at memory location 0x002ff558..
First-chance exception at 0x000007fefe18cacd in ConsoleApp.exe: Microsoft C++ exception: [rethrow] at memory location 0x00000000..
First-chance exception at 0x000007fefe18cacd in ConsoleApp.exe: Microsoft C++ exception: HRException * __ptr64 at memory location 0x002ff558..

ICorDebug를 처음 사용해 보는 저 같은 초보 프로그래머가 흔히 겪는 실수일 텐데요. ICorDebugProcess를 얻기 전에, 반드시 콜백 핸들러를 지정해 주어야만 저런 예외가 발생하지 않습니다.

그래서, 다른 소스 코드를 참조한 결과 일반적으로 다음과 같은 수순으로 연결되는 것을 볼 수 있었습니다.

pCorDebug->SetManagedHandler(this); // ICorDebugManagedCallback를 구현한 인스턴스를 설정

hr = pCorDebug->CanLaunchOrAttach(processId, false);
if (hr != S_OK)
{
    break;
}

CComPtr<ICorDebugProcess> pCorDebugProcess;
hr = pCorDebug->DebugActiveProcess(processId, false, &pCorDebugProcess);
if (hr != S_OK)
{
    break;
}

일단, 위와 같이 ICorDebugProcess로 대상 프로세스에 Attach를 정상적으로 시키면, 이제 현재 생성되어 있는 스레드 목록을 얻어야 하는데요. 이 작업은 ICorDebugController를 얻음으로써 가능합니다.

CComPtr<ICorDebugProcess> pCorDebugProcess;
hr = pCorDebug->DebugActiveProcess(dwPid, false, &pCorDebugProcess);
if (hr != S_OK)
{
    break;
}

pCorDebugProcess->Continue(FALSE);

bool exit = false;
CComQIPtr<ICorDebugController> pCorDebugController = pCorDebugProcess;

ICorDebugController::EnumerateThreads 메서드를 이용하여 스레드를 열람할 수 있는데, 주의할 것은 곧바로 이 상태로 진입해서는 안된다는 것입니다. ICorDebugManagedCallback::CreateThread 이벤트 단계와 협업을 해야 하는데, 이 부분은 MSE 소스 코드를 보고서야 알게 된 사항이어서 이로 인한 문제를 해결하기까지 처음에 시간이 많이 걸렸습니다.

즉, 다음과 같이 WaitForSingleObject 등의 수단을 이용해서 기다렸다가 스레드를 열람해야 하는데,

::WaitForSingleObject(_threadStartEventHandle, INFINITE);

CComPtr<ICorDebugThreadEnum> pThreadsEnum;
hr = pCorDebugController->EnumerateThreads(&pThreadsEnum);
if (hr != S_OK)
{
    exit = true;
    break;
}

이에 대한 동기화 이벤트는 다음과 같이 ICorDebugManagedCallback::CreateThread 단계를 구성해서 처리를 해줘야 합니다.

virtual HRESULT STDMETHODCALLTYPE CreateThread(ICorDebugAppDomain *pAppDomain, ICorDebugThread *thread) 
{
    HRESULT hr;

    do
    {
        CComPtr<ICorDebugProcess> pProcess;
        hr = thread->GetProcess(&pProcess);
        if (hr != S_OK || pProcess == NULL)
        {
            break;
        }

        BOOL bQueued = FALSE;
        hr = pProcess->HasQueuedCallbacks(NULL, &bQueued);
        if (hr != S_OK)
        {
            break;
        }

        if (bQueued == FALSE)
        {
            ((CManagedStackTrace *)this)->SignalAttachedProcess(); // ::SetEvent(_threadStartEventHandle);
            return S_OK;
        }
        else
        {
            pAppDomain->Continue(FALSE);
            return S_OK;
        }

    } while (false);

    ((CManagedStackTrace *)this)->SignalAttachedProcess();  // ::SetEvent(_threadStartEventHandle);

    return S_OK;
}

솔직히, 위와 같은 처리 절차는 그냥은 알기 힘들고 MSE 소스 코드가 있었기 때문에 가능한 것 같습니다. 참고로, HasQueuedCallbacks에 대한 처리는 다음의 문서를 참조하십시오.

Using ICorDebugProcess::HasQueuedCallbacks
; http://blogs.msdn.com/b/jmstall/archive/2005/07/27/hasqueuedcallbacks.aspx
; https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.cordebuginterop.icordebugprocess.hasqueuedcallbacks

그다음, ICorDebugManagedCallback의 중요 이벤트마다 적절하게 Continue 메서드를 호출해 주어야 하는 문제가 있습니다. 이를 몰랐을 때는, 처리하는 중에 다음과 같이 ICorDebugManagedCallback::DebuggerError에서 오류를 만나서 당황했었는데요.

virtual HRESULT STDMETHODCALLTYPE DebuggerError(ICorDebugProcess *pProcess, HRESULT errorHR, DWORD errorCode)  
{
    // CORDBG_E_INTEROP_NOT_SUPPORTED 0x8013134D (-2146233523) 오류 발생
    return E_NOTIMPL;
}

다음과 같은 콜백 메서드에 대해서 S_OK 반환과 Continue 메서드를 호출해 주어야만 했습니다. (역시 MSE 소스 코드를 보고 알았습니다.)

virtual HRESULT STDMETHODCALLTYPE CreateProcess(ICorDebugProcess *pProcess) 
{
    pProcess->Continue(FALSE);
    return S_OK;
}

virtual HRESULT STDMETHODCALLTYPE LoadModule(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule)  
{
    pAppDomain->Continue(FALSE);
    return S_OK;
}

virtual HRESULT STDMETHODCALLTYPE CreateAppDomain(ICorDebugProcess *pProcess, ICorDebugAppDomain *pAppDomain) 
{
    pAppDomain->Attach();
    pProcess->Continue(FALSE);
    return S_OK;
}

virtual HRESULT STDMETHODCALLTYPE DebuggerError(ICorDebugProcess *pProcess, HRESULT errorHR, DWORD errorCode)  
{
    pProcess->Continue(FALSE);
    return S_OK;
}

또 한 가지 주의할 점은 위에서 pProcess->Continue 등의 함수에서 TRUE/FALSE 값은 fIsOutOfBand 값으로 나오는데요. 이에 대해서는 다음의 MSDN 문서를 봐야 합니다.

ICorDebugController::Continue Method
; https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugcontroller-continue-method

Continue continues the process after a call to the ICorDebugController::Stop method.

When doing mixed-mode debugging, do not call Continue on the Win32 event thread unless you are continuing from an out-of-band event.

An in-band event is either a managed event or a normal unmanaged event during which the debugger supports interaction with the managed state of the process. In this case, the debugger receives the ICorDebugUnmanagedCallback::DebugEvent callback with its fOutOfBand parameter set to false.
An out-of-band event is an unmanaged event during which interaction with the managed state of the process is impossible while the process is stopped due to the event. In this case, the debugger receives the ICorDebugUnmanagedCallback::DebugEvent callback with its fOutOfBand parameter set to true.


결국 Continue 인자에 정상적인 TRUE/FALSE 값을 넘겨주려면 ICorDebugUnmanagedCallback도 구현해 주어야 하고, 위에서 ICorDebug::SetManagedHandler로 ICorDebugManagedCallback를 등록해 주었던 것처럼 ICorDebug::SetUnmanagedHandler로 ICorDebugUnmanagedCallback도 등록해서 처리해 주어야 합니다.

일단, 제 경우에는 테스트 수준이다 보니 무조건 FALSE로 넘겼으나... 나중에 그거마저 구현을 한 소스를 기회가 되면 올리겠습니다.

이 정도만 했으면 일단 .NET 4.0 응용 프로그램에 대해서도 정상적으로 Call Stack을 얻을 수 있습니다. 나머지 소스 코드는 MSE C# 버전의 작업들을 지리하게 C/C++ 코드로 다시 작성한 코드들의 나열이기 때문에 이 글에서는 생략했지만, 첨부된 소스 코드에는 넣어두었으므로 참고하시기 바랍니다.

마지막으로, 제가 이번 테스트를 진행하면서 어려움을 겪은 것이 하나 더 있었는데 이를 말씀드려야 할 것 같습니다. ^^

위의 소스 코드로 작업을 하는데, 때로는 정상적으로 동작하다가도 어쩌다가 ICorDebug::DebugActiveProcess를 호출했을 때 반환값이 hr == 0x8013132e으로 나오는 이상한 문제가 발생했습니다.

검색도 해보았지만, 건수가 하나만 나올 뿐 답이 없더군요. ^^;

Error: attaching for debug to external process 
; http://community.sharpdevelop.net/forums/p/8929/24848.aspx

훗날, 저처럼 ICorDebug 가지고 놀다가 이런 시행 착오를 거치지 않으시길 바라면서 정리하자면... ^^;

바로, 대상 응용 프로그램이 이미 디버거에 붙어(Attach)있는 상태인 경우에 발생합니다. 제 경우에는, ICorDebug::DebugActiveProcess를 호출하는 응용 프로그램을 디버깅하는 목적으로 그 자신의 콜 스택을 남기도록 스스로 프로세스를 재생성해서 테스트 하는 경우였습니다.

2개의 디버거가 하나의 EXE 프로세스를 제어할 수 없다는 것을 생각하면 응당 이해는 되지만... 습관대로 디버거를 시작했다가 저런 결과가 나오면 오류가 무엇인지 감이 안 잡히더군요. ^^ 어쨌든, 테스트하실 때 이미 Visual Studio로 디버깅 중인 프로세스를 다시 DebugActiveProcess로 테스트 하는 실수는 하지 마시기 바랍니다.

그나저나... 꽤나 쉽지 않은 분야인 것 같습니다. 디버깅이란 분야는!




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/21/2023]

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

비밀번호

댓글 작성자
 



2012-02-26 06시16분
소스 코드를 아래의 글에 따라 업그레이드를 했습니다.

Implementing ICorDebugManagedCallback2
; (broken) http://blogs.msdn.com/b/andrew_richards/archive/2012/01/30/implementing-icordebugmanagedcallback2.aspx

위의 코드를 보니, Continue 메서드에 무조건 false 값을 주는 것으로 봐서 ICorDebugUnmanagedCallback과의 연동이 꼭 필요한 것은 아닌 것 같습니다. (물론, 추측입니다.)

일단 테스트 상으로는, .NET 2.0 x86/x64, .NET 4.0 x86/x64에 대해서 잘 동작합니다.
정성태
2020-08-12 11시17분
정성태

1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13501정성태12/25/20232087개발 환경 구성: 700. WSL + uwsgi - IPv6로 바인딩하는 방법
13500정성태12/24/20232178디버깅 기술: 194. Windbg - x64 가상 주소를 물리 주소로 변환
13498정성태12/23/20232822닷넷: 2186. 한국투자증권 KIS Developers OpenAPI의 C# 래퍼 버전 - eFriendOpenAPI NuGet 패키지
13497정성태12/22/20232294오류 유형: 885. Visual Studiio - error : Could not connect to the remote system. Please verify your connection settings, and that your machine is on the network and reachable.
13496정성태12/21/20232307Linux: 66. 리눅스 - 실행 중인 프로세스 내부의 환경변수 설정을 구하는 방법 (gdb)
13495정성태12/20/20232317Linux: 65. clang++로 공유 라이브러리의 -static 옵션 빌드가 가능할까요?
13494정성태12/20/20232500Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
13493정성태12/19/20232546닷넷: 2185. C# - object를 QueryString으로 직렬화하는 방법
13492정성태12/19/20232261개발 환경 구성: 699. WSL에 nopCommerce 예제 구성
13491정성태12/19/20232232Linux: 63. 리눅스 - 다중 그룹 또는 사용자를 리소스에 권한 부여
13490정성태12/19/20232345개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
13489정성태12/19/20232133개발 환경 구성: 697. GoLand에서 ldflags 지정 방법
13488정성태12/18/20232067오류 유형: 884. HTTP 500.0 - 명령행에서 실행한 ASP.NET Core 응용 프로그램을 실행하는 방법
13487정성태12/16/20232381개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행 [1]
13486정성태12/15/20232194개발 환경 구성: 695. Nuget config 파일에 값 설정/삭제 방법
13485정성태12/15/20232088오류 유형: 883. dotnet build/restore - error : Root element is missing
13484정성태12/14/20232161개발 환경 구성: 694. Windows 디렉터리 경로를 WSL의 /mnt 포맷으로 구하는 방법
13483정성태12/14/20232296닷넷: 2184. C# - 하나의 resource 파일을 여러 프로그램에서 (AOT 시에도) 사용하는 방법파일 다운로드1
13482정성태12/13/20232817닷넷: 2183. C# - eFriend Expert OCX 예제를 .NET Core/5+ Console App에서 사용하는 방법 [2]파일 다운로드1
13481정성태12/13/20232266개발 환경 구성: 693. msbuild - .NET Core/5+ 프로젝트에서 resgen을 이용한 리소스 파일 생성 방법파일 다운로드1
13480정성태12/12/20232602개발 환경 구성: 692. Windows WSL 2 + Chrome 웹 브라우저 설치
13479정성태12/11/20232299개발 환경 구성: 691. WSL 2 (Ubuntu) + nginx 환경 설정
13477정성태12/8/20232480닷넷: 2182. C# - .NET 7부터 추가된 Int128, UInt128 [1]파일 다운로드1
13476정성태12/8/20232206닷넷: 2181. C# - .NET 8 JsonStringEnumConverter의 AOT를 위한 개선파일 다운로드1
13475정성태12/7/20232278닷넷: 2180. .NET 8 - 함수 포인터에 대한 Reflection 정보 조회파일 다운로드1
13474정성태12/6/20232133개발 환경 구성: 690. 닷넷 코어/5+ 버전의 ilasm/ildasm 실행 파일 구하는 방법 - 두 번째 이야기
1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...