Dotnet

.NET · C# 메모 — async/await 에서 4.0 으로 내려오기, C++ DLL 마샬링, WPF 컨트롤 PNG 저장

목차

2015년에 윈도우 앱을 하나 만들면서 남겼던 메모 세 개를 합쳤다.

async/await 로 짰다가 4.0 으로 내려온 이야기

윈도우 앱을 개발할 일이 생겼다. .NET 4.5부터 async 문법이 새로 들어갔다 해서 이왕 하는 거 4.5.2 로 만들기로 했다. 엄청나게 편리하다.

async, await 두 개가 중요하다. 특히 UI 프로그램에서 background thread 와 main thread 의 동기화를 쉽게 해준다.

아래는 id/pwd 를 입력받아서 서버 통신으로 인증하는 코드다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private async void btnLogin_Click(object sender, RoutedEventArgs e)
{
    string id = tbEmail.Text;
    string pwd = tbPassword.Password;

    SetControlEnableState(false);
    bool isSuccess = await Task.Run<bool>(() =>
    {
        try
        {
                    // 여기가 네트워크 통신을 하는 부분이다.
            _dataCore.DoLogin(id, pwd);
            return true;
        }
        catch (Exception x)
        {
            ErrorHandler.ErrorDump(x, true);
            return false;
        }
        });

    if (!isSuccess)
    {
        SetControlEnableState(true);
        return;
    }
}

보면 알겠지만 클라이언트 이벤트에서 바로, 별도 쓰레드 동기화 없이 로그인 처리를 다 끝냈다. InvokeDispatcher 니 하는 게 하나도 없다.

그리고 테스트를 진행하는데 .NET Framework 4.5 깔린 PC 가 많이 없었다.

그래서 4.0 으로 BackgroundWorker 를 써서 재개발했다. 그지 같네.. 화면이 세 개인 프로그램이라 그나마 다행이었다.

교훈이라면, 문법이 편한 것보다 배포 대상 PC 의 런타임 버전을 먼저 확인해야 한다는 것.

C# 에서 C++ DLL 에 LPCTSTR 넘기기

C++ 로 된 dll 을 C# 에서 호출할 때의 예제다.

C++ 쪽

1
extern "C" __declspec(dllexport) int test(LPCTSTR szFileName);
1
2
3
int test(LPCTSTR szFileName) {
     return 0;
}

C# 쪽

1
2
3
4
5
6
7
8
9
[DllImport("sampleLib.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int test( [MarshalAs(UnmanagedType.LPWStr)] string szFileName);

private void button2_Click(object sender, EventArgs e)
{
    string szFileName = @"c:\filename.txt";
    int result = test(ticketName);
    Debug.WriteLine("test=" + result);
}

CallingConvention = CallingConvention.Cdecl[MarshalAs(UnmanagedType.LPWStr)] 이 두 개가 중요하다. 둘 중 하나만 빠져도 호출이 깨지거나 문자열이 깨진다.

반대로 C++ 에서 string 을 받을 때

IntPtr 로 받아서 변환한다.

1
2
3
4
IntPtr ptr = test("111111");
string data = Marshal.PtrToStringAnsi(ptr);
// 꼭 해제 한다
Marshal.FreeHGlobal(data);

해제를 빼먹으면 샌다.

WPF 컨트롤을 PNG 로 저장

예전에 하던 대로 System.Drawing 을 쓰려고 했는데, WPF 에 이런 기능이 이미 있었다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// uiPage.ren
public void DoPageToPng(string fileName)
{
    RenderTargetBitmap rtb = new RenderTargetBitmap((int)uiPage.ActualWidth, (int)uiPage.ActualHeight, 96, 96, PixelFormats.Pbgra32);
    rtb.Render(uiPage);

    PngBitmapEncoder png2 = new PngBitmapEncoder();
    png2.Frames.Add(BitmapFrame.Create(rtb));
    using (MemoryStream stream = new MemoryStream())
    {
        png2.Save(stream);
        using (System.Drawing.Image image = System.Drawing.Image.FromStream(stream))
        {
            image.Save(fileName);
        }
    }
}

RenderTargetBitmap 이 핵심이다. 화면에 보이는 컨트롤을 그대로 비트맵으로 렌더링해준다.

정리

  • async/await 는 UI 쓰레드 동기화를 확 줄여준다. 다만 배포 대상 런타임을 먼저 확인할 것
  • C++ dll 호출은 CallingConvention.Cdecl + MarshalAs(UnmanagedType.LPWStr)
  • 받은 IntPtrMarshal.FreeHGlobal 로 꼭 해제
  • WPF 컨트롤 캡처는 RenderTargetBitmap