Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 4개 있습니다.)
.NET Framework: 975. .NET Core를 직접 호스팅해 (runtimeconfig.json 없이) EXE만 배포해 실행
; https://www.sysnet.pe.kr/2/0/12427

.NET Framework: 992. C# - .NET Core 3.0 이상부터 제공하는 runtimeOptions의 rollForward 옵션
; https://www.sysnet.pe.kr/2/0/12471

.NET Framework: 1165. .NET Core/5+ 빌드 시 runtimeconfig.json에 설정을 반영하는 방법
; https://www.sysnet.pe.kr/2/0/12983

.NET Framework: 2096. .NET Core/5+ - PublishSingleFile 유형에 대한 runtimeconfig.json 설정
; https://www.sysnet.pe.kr/2/0/13265




.NET Core를 직접 호스팅해 (runtimeconfig.json 없이) EXE만 배포해 실행

(이번 주제는, self-contained 방식이 아닌 framework-dependent인 경우 적용할 수 있습니다.)

닷넷 코어에서 runtimeconfig.json을 없앨 수 있을까요?

닷넷코어 빌드 시 runtimeconfig 파일을 없앨 수 있는지..
; https://www.sysnet.pe.kr/3/0/5352

예전에 유사하게 닷넷 프레임워크에 대해서도 이런 처리를 했었는데요,

.NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/1746

마찬가지로 .NET Core를 직접 호스팅하면 runtimeconfig.json을 배포하지 않고도 (정확히는 실행 파일에 포함시켜 배포 후 런타임에 풀어내는 식으로) 실행할 수 있습니다. 이를 위해 호스팅 관련 공식 문서들을 보면,

Native hosting
; https://github.com/dotnet/runtime/blob/master/docs/design/features/native-hosting.md

Sample .NET Core Hosts
; https://github.com/dotnet/samples/tree/master/core/hosting

Write a custom .NET Core host to control the .NET runtime from your native code
; https://learn.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting

github에 예제가 2가지로 나뉘는데,

  • HostWithHostFxr - .NET Core 3.0에서 도입된 nethost/hostfxr API를 이용(.NET 3.0 이후의 호스팅 용도로 권장)
  • HostWithCoreClrHost - coreclrhost.h에서 제공하는 API를 이용(기존 .NET Core 2.2 이하의 호스팅에서 선호)

이 글에서는 HostWithHostFxr 방식으로 실습해 보겠습니다.




.NET Core/5 런타임을 호스팅하기 위한 첫 번째 작업은 hostfxr.dll을 찾는 것입니다. 왜냐하면 그 DLL 내에 런타임 호스팅을 위한 API들이 정의되어 있기 때문입니다. 그리고 그 hostfx.dll을 찾는 작업을 nethost에서 담당하는데 이미 nethost를 다루는 것에 대해서는 다음의 글에서 설명했습니다.

Visual C++ - .NET Core의 nethost.lib와 정적 링크
; https://www.sysnet.pe.kr/2/0/12419

일단 위의 단계까지 마무리 된 걸로 가정해 다음의 소스 코드에서 시작해 보겠습니다.

struct get_hostfxr_parameters {
    size_t size;
    const wchar_t* assembly_path;
    const wchar_t* dotnet_root;
};

extern "C" int __stdcall get_hostfxr_path(
    wchar_t* buffer,
    size_t * buffer_size,
    const struct get_hostfxr_parameters* parameters);

#include <iostream>
#include <Windows.h>
#include <shlwapi.h>
#include <assert.h>

#include <fstream>
#include <string>

#pragma comment(lib, "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Host.win-x64\\5.0.0\\runtimes\\win-x64\\native\\libnethost.lib")

int main()
{
    wchar_t hostfxr_path[MAX_PATH];

    {
        size_t buffer_size = sizeof(hostfxr_path) / sizeof(wchar_t);

        int rc = get_hostfxr_path(hostfxr_path, &buffer_size, nullptr);
        // buffer == C:\Program Files\dotnet\host\fxr\5.0.0\hostfxr.dll
    }

    return 0;
}

이후 순서는 문서에 따라 단계별로 하시면 됩니다. ^^

Step 1 - Load hostfxr and get exported hosting functions
; https://learn.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting#step-1---load-hostfxr-and-get-exported-hosting-functions

Step 2 - Initialize and start the .NET Core runtime
; https://learn.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting#step-2---initialize-and-start-the-net-core-runtime

Step 3 - Load managed assembly and get function pointer to a managed method
; https://learn.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting#step-3---load-managed-assembly-and-get-function-pointer-to-a-managed-method

Step 4 - Run managed code!
; https://learn.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting#step-4---run-managed-code

따라서, hostfxr.dll로부터 호스팅 관련 API들을 가져온 후,

...[생략]...

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

#define NETHOST_CALLTYPE __stdcall

enum hostfxr_delegate_type
{
    hdt_com_activation,
    hdt_load_in_memory_assembly,
    hdt_winrt_activation,
    hdt_com_register,
    hdt_com_unregister,
    hdt_load_assembly_and_get_function_pointer
};

typedef struct hostfxr_initialize_parameters
{
    size_t size;
    const wchar_t* host_path;
    const wchar_t* dotnet_root;
} hostfxr_initialize_parameters;

typedef void* hostfxr_handle;

typedef int32_t(NETHOST_CALLTYPE* hostfxr_initialize_for_runtime_config_fn)(
    const wchar_t* runtime_config_path,
    const hostfxr_initialize_parameters* parameters,
    /*out*/ hostfxr_handle* host_context_handle);
typedef int32_t(NETHOST_CALLTYPE* hostfxr_get_runtime_delegate_fn)(
    const hostfxr_handle host_context_handle,
    enum hostfxr_delegate_type type,
    /*out*/ void** delegate);
typedef int32_t(NETHOST_CALLTYPE* hostfxr_close_fn)(const hostfxr_handle host_context_handle);

static hostfxr_initialize_for_runtime_config_fn init_fptr;
static hostfxr_get_runtime_delegate_fn get_delegate_fptr;
static hostfxr_close_fn close_fptr;

#pragma comment(lib, "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Host.win-x64\\5.0.0\\runtimes\\win-x64\\native\\libnethost.lib")

static void* load_library(const wchar_t* path)
{
    assert(path != NULL);
    HMODULE h = LoadLibraryW(path);
    assert(h != NULL);
    return (void*)h;
}

static void* get_export(void* h, const char* name)
{
    assert(h != NULL && name != NULL);
    void* f = GetProcAddress((HMODULE)h, name);
    assert(f != NULL);
    return f;
}

int main()
{
    {
        // ...[생략]...

        void* lib = load_library(hostfxr_path);
        init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config");
        get_delegate_fptr = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate");
        close_fptr = (hostfxr_close_fn)get_export(lib, "hostfxr_close");
    }

    return 0;
}

런타임 호스팅을 시작하고 끝내는 것을 다음과 같이 할 수 있습니다.

#define NETHOST_SUCCESS 0

static bool is_failure(int rc)
{
    // The CLR hosting API uses the Win32 HRESULT scheme. This means
    // the high order bit indicates an error and S_FALSE (1) can be returned
    // and is _not_ a failure.
    return (rc < NETHOST_SUCCESS);
}

// ...[생략]...

int main()
{  
    // ...[생략]...

    hostfxr_handle cxt = nullptr;
    int rc = 0;
    wchar_t config_path[MAX_PATH] = L"...[path_to_runtimeconfig.json]...";

    do
    {
        rc = init_fptr(config_path, nullptr, &cxt);
        if (is_failure(rc) || cxt == nullptr)
        {
            break;
        }

    } while (false);

    if (cxt != nullptr)
    {
        close_fptr(cxt);
    }
}

그리고, 바로 저 init_fptr의 첫 번째 인자에 들어가는 것이 "runtimeconfig.json"의 경로입니다. 그럼, 간단하게 저 파일을 런타임 시에 만들어도 상관없는 것입니다. 원칙적으로는 get_hostfxr_path에서 구한 .NET Core 런타임의 버전을 설정하는 것이 맞겠지만 여기서는 소스 코드의 간결함을 위해 그냥 5.0.0으로 고정시켜 진행합니다.

// ...[생략]...

void write_and_get_config_path(wchar_t* config_path, size_t config_path_size, wchar_t* hostfxr_path)
{
    std::string txt = 
"{\n"
"    \"runtimeOptions\": {\n"
"        \"tfm\": \"net5.0\",\n"
"            \"framework\" : {\n"
"            \"name\": \"Microsoft.NETCore.App\",\n"
"            \"version\" : \"5.0.0\"\n"
"        }\n"
"    }\n"
"}\n";
    
    HMODULE hModule = ::GetModuleHandle(NULL);

    ::GetModuleFileName(hModule, config_path, (DWORD)config_path_size);
    ::PathCchRemoveFileSpec(config_path, config_path_size);
    ::PathCchAppendEx(config_path, config_path_size, L"SampleHostApp.runtimeconfig.json", 0);

    std::ofstream ofs;
    ofs.open(config_path);

    ofs << txt;

    ofs.close();
}

int main()
{
    // ...[생략]...

    wchar_t config_path[MAX_PATH];

    {
        size_t config_path_size = sizeof(buffer) / sizeof(wchar_t);

        write_and_get_config_path(config_path, config_path_size, buffer);
    }

    // ...[생략]...
}

이걸로 일단, runtimeconfig.json은 해결했고 그다음 우리가 만든 .NET DLL에서 시작점을 지정해 함수 포인터를 얻는 작업을 해야 합니다.

int main()
{
    // ...[생략]...

    const wchar_t* dotnet_type = L"SampleHostApp.Program, SampleHostApp";
    const wchar_t* dotnet_type_method = L"Main";
    const wchar_t* dotnet_delegate_type = L"SampleHostApp.MainDelegate, SampleHostApp";

    void* func = nullptr;
    rc = get_managed_export_fptr(
        assembly_path,
        dotnet_type,
        dotnet_type_method,
        dotnet_delegate_type,
        nullptr,
        &func);
    if (is_failure(rc) || func == nullptr)
    {
        break;
    }

    // ...[생략]...
}

여기서 예제로 사용하는 SampleHostApp.dll은 .NET Core 3.1 프로젝트로 아래의 예제 코드를 빌드한 것입니다.

using System;

namespace SampleHostApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("[NETCORE] Hello World!");
      
            Console.WriteLine("# of args: " + args?.Length);
            foreach (string arg in args ?? Array.Empty<string>())
            {
                Console.WriteLine(arg);
            }
        }
    }

    public delegate void MainDelegate(string[] args);
}

또한 지난번과 마찬가지로 이들은 호스팅을 담당하는 C/C++ EXE 내에 리소스로 포함할 것이므로 런타임 시에 다음과 같은 코드로 풀어내야 합니다.

// ...[생략]...

void write_and_get_assembly_path(wchar_t* assembly_path, size_t asm_path_size)
{
    HMODULE hModule = ::GetModuleHandle(NULL);

    ::GetModuleFileName(hModule, assembly_path, asm_path_size);
    ::PathCchRemoveFileSpec(assembly_path, asm_path_size);
    ::PathCchAppendEx(assembly_path, asm_path_size, L"SampleHostApp.dll", 0);

    HRSRC hResource = FindResource(hModule, MAKEINTRESOURCE(IDR_DLLFILE1), L"DLLFILE");
    HGLOBAL hMemory = LoadResource(hModule, hResource);
    DWORD dwSize = SizeofResource(hModule, hResource);
    LPVOID lpAddress = LockResource(hMemory);

    std::ofstream ofs;
    ofs.open(assembly_path, std::ios::binary);

    ofs.write((char*)lpAddress, dwSize);

    ofs.close();
}

int main()
{
    // ...[생략]...

    wchar_t assembly_path[MAX_PATH];

    {
        size_t asm_path_size = sizeof(assembly_path) / sizeof(wchar_t);
        write_and_get_assembly_path(assembly_path, asm_path_size);
    }

    // ...[생략]...
}

자, 그럼 이걸로 끝이군요, 함수 포인터도 잘 구했으니 이제 다음과 같은 식으로 호출만 하면 됩니다.

typedef void (*main_ptr)(intptr_t);
main_ptr mainFunc = (main_ptr)func;

mainFunc(0);




그런데, 여기서 한 가지 문제가 있습니다. 바로 닷넷의 Main 메서드에 전달할 "string [] args" 인자 값을 C/C++에서 딱히 어떻게 구성해야 하는 모르겠습니다. (혹시, 아시는 분은 덧글 부탁드립니다.)

그래서 위의 경우처럼 실행하면 Main 메서드의 string [] args에 null이 전달되어 다음과 같은 식의 실행 결과를 얻게 됩니다.

E:\DotNetSamples\x64\Debug> netcore_host.exe test
[NETCORE] Hello World!
# of args:

하지만, 이렇게 Main 메서드 만을 부르는 식이라면 더 간단하게 호스팅을 처리하는 방법이 hostfx.dll에 있습니다. 바로 hostfxr_main 함수인데, 이것을 사용하면 별다른 호스팅 코딩 없이 모두 자동화되어 처리가 됩니다.

int main()
{
    wchar_t hostfxr_path[MAX_PATH];

    {
        size_t buffer_size = sizeof(hostfxr_path) / sizeof(wchar_t);

        int rc = get_hostfxr_path(hostfxr_path, &buffer_size, nullptr);
        if (is_failure(rc))
            noreturn_runtime_load_failure(rc);

        void* lib = load_library(hostfxr_path);

        hostfxr_main_fptr = (hostfxr_main_fn)get_export(lib, "hostfxr_main");
    }

    wchar_t config_path[MAX_PATH];

    {
        size_t config_path_size = sizeof(config_path) / sizeof(wchar_t);
        write_and_get_config_path(config_path, config_path_size, hostfxr_path);
    }

    wchar_t assembly_path[MAX_PATH];

    {
        size_t asm_path_size = sizeof(assembly_path) / sizeof(wchar_t);
        write_and_get_assembly_path(assembly_path, asm_path_size);
    }

    int nArgs = 0;
    LPWSTR* szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);

    PCWSTR* argv = new PCWSTR[2 + ((short)nArgs) - 1];
    argv[0] = config_path; // 첫 번째 요소에 runtimeconfig.json 파일 경로
    argv[1] = assembly_path; // 두 번째 요소에 .NET DLL 경로

    for (int i = 0; i < nArgs - 1; i++)
    {
        argv[2 + i] = szArglist[i + 1]; // 세 번째 이후는 명령행에 전달된 인자
    }

    hostfxr_main_fptr(2 + (nArgs - 1), argv); // 내부에서 자동으로 호스팅도 시작하고 Main 메서드도 호출

    delete[] argv;
    LocalFree(szArglist);

    if (cxt != nullptr)
    {
        close_fptr(cxt);
    }

    return 0;
}




참고로, 위의 예에서 get_managed_export_fptr를 이용한 호출 방식은 내부에 반드시 델리게이트 타입이 정의되어 있어야 합니다. 예제의 경우 그래서 MainDelegate 타입을 포함했던 것입니다.

마찬가지로 닷넷 호스팅 문서의 예제에서는,

HostWithHostFxr
; https://github.com/dotnet/samples/tree/master/core/hosting/HostWithHostFxr

ComponentEntryPoint라는 델리게이트를 정의해 놓고 그 양식에 맞는 닷넷 메서드를 호출하고 있습니다.

public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);

재미있는 것은, .NET 5에서의 함수 포인터가 적용되면서 델리게이트를 생략할 수 있게 되었다는 점입니다. 이에 대한 예제는 DNNE에서 제공하는 platform.c의 get_callable_managed_function 코드에서 확인할 수 있습니다.

// ...[생략]...

#define UNMANAGEDCALLERSONLY_METHOD ((const char_t*)-1)

// ...[생략]...

void* get_callable_managed_function(
    const char_t* dotnet_type,
    const char_t* dotnet_type_method,
    const char_t* dotnet_delegate_type)
{
    assert(dotnet_type && dotnet_type_method);

    // Check if the runtime has already been prepared.
    if (!get_managed_export_fptr)
    {
        prepare_runtime();
        assert(get_managed_export_fptr != NULL);
    }

    char_t buffer[DNNE_MAX_PATH];
    const char_t assembly_filename[] = DNNE_STR(DNNE_TOSTRING(DNNE_ASSEMBLY_NAME)) DNNE_STR(".dll");
    const char_t* assembly_path = NULL;
    int rc = get_current_dir_filepath(DNNE_ARRAY_SIZE(buffer), buffer, DNNE_ARRAY_SIZE(assembly_filename), assembly_filename, &assembly_path);
    if (is_failure(rc))
        noreturn_export_load_failure(rc);

    // Function pointer to managed function
    void* func = NULL;
    rc = get_managed_export_fptr(
        assembly_path,
        dotnet_type,
        dotnet_type_method,
        dotnet_delegate_type,
        NULL,
        &func);

    if (is_failure(rc))
        noreturn_export_load_failure(rc);

    return func;
}

void* get_fast_callable_managed_function(
    const char_t* dotnet_type,
    const char_t* dotnet_type_method)
{
    return get_callable_managed_function(dotnet_type, dotnet_type_method, UNMANAGEDCALLERSONLY_METHOD);
    // return get_callable_managed_function(dotnet_type, dotnet_type_method, 0x05);
}

즉, 닷넷 모듈에 정의한 메서드 중 UnmanagedCallersOnly 특성이 부여된 경우라면 그 메서드를 호출할 때는 델리게이트 타입을 명시할 필요 없이 -1 값을 전달하면 되는 것입니다.

이 글의 예제 코드는 github에 올려 두었습니다.

DotNetSamples/Cpp/netcore_host
; https://github.com/stjeong/DotNetSamples/tree/master/Cpp/netcore_host

개선해야 할 점이 있다면, 리눅스를 지원하지 않아 윈도우에서만 실행이 됩니다. (원하시는 분은 PR 넣으셔도 됩니다. ^^)




만약 nethost에 대한 의존성이 싫다면, 사실 그냥 만들어도 됩니다. 중요한 것은, "C:\Program Files\dotnet\host\fxr\5.0.0\hostfxr.dll" 경로를 찾는 것이기 때문에 윈도우라면 레지스트리 또는 %ProgramFiles%에서 검색하면 (100%는 아니겠지만) 대부분의 경우 해결이 됩니다. 물론, nethost 만큼 다양한 방식으로 검색해 반환하려면 꽤나 코드가 들어갈 텐데요, 원한다면 nethost 관련 소스 코드는 다음의 경로에서 찾을 수 있습니다.

runtime/src/installer/corehost/cli/nethost/CMakeLists.txt
; https://github.com/dotnet/runtime/blob/master/src/installer/corehost/cli/nethost/CMakeLists.txt

runtime/src/installer/corehost/cli/nethost/nethost.cpp
; https://github.com/dotnet/runtime/blob/master/src/installer/corehost/cli/nethost/nethost.cpp

runtime/src/installer/corehost/cli/fxr_resolver.cpp
; https://github.com/dotnet/runtime/blob/master/src/installer/corehost/cli/fxr_resolver.cpp

core-setup/src/corehost/common/pal.windows.cpp
; https://github.com/dotnet/core-setup/blob/master/src/corehost/common/pal.windows.cpp

runtime/src/installer/corehost/cli/apphost/static/hostfxr_resolver.cpp
; https://github.com/dotnet/runtime/blob/master/src/installer/corehost/cli/apphost/static/hostfxr_resolver.cpp

실제로 위의 코드를 보면, nethost에서 %ProgramFiles% 또는 %ProgramFiles(x86)%의 경로에 "host" + "fxr" 문자열의 하위 경로 2개가 존재하면 그 이하 폴더를 열거하는 식의 코드를 볼 수 있습니다.

혹은, nethost를 비롯해 닷넷 관련 모듈은 "COREHOST_TRACE=1" 환경 변수로 인한 로그를 꽤나 자세하게 남기므로 그 로그를 통해 nethost의 동작을 유추하는 것도 가능합니다. (첨부 파일은 제 컴퓨터에서 실행한 예제 코드의 trace 로그입니다.)

그나저나, 정리해 놓고 나니 한 가지 아쉬움이 남는군요. ^^ 닷넷 프레임워크의 경우 DLL을 메모리로부터 로드하는 AppDomain.Load 버전을 제공하므로 정말로 한 개의 모듈로 실행하는 것이 가능했습니다. 반면, 닷넷 코어는 runtimeconfig.json과 닷넷 DLL에 대한 경로를 필요로 하므로 사용 전 반드시 파일에 써야 하는 단점이 있습니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 10/4/2024]

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)
1755정성태9/22/201434269오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201424590VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201420601오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201441061Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201438926.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201423827.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201423730.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201425379개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201428401오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201426091.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201423032개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201431042.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201420983오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201426955개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201421330.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201432499.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201426501.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201422042.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201419750VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201425566VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201418155.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201419810오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201426340.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201434462Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201427070개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
1730정성태8/11/201422154개발 환경 구성: 234. Royal TS의 터미널(Terminal) 연결에서 한글이 깨지는 현상 해결 방법
... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...