Dotnet

.NET and C# Notes — Falling Back from async/await, C++ DLL Marshalling, Saving a WPF Control as PNG

Contents

Three notes from building a Windows app in 2015, merged into one page.

Writing it with async/await, then falling back to 4.0

A Windows app came up. Since .NET 4.5 introduced the async syntax, I decided to build it on 4.5.2 while I was at it. It is remarkably convenient.

async and await are the whole story. In a UI program they make synchronizing between a background thread and the main thread trivial.

Here is the login flow: take an id and password, authenticate against the server.

 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
        {
                    // this is the part that talks to the network
            _dataCore.DoLogin(id, pwd);
            return true;
        }
        catch (Exception x)
        {
            ErrorHandler.ErrorDump(x, true);
            return false;
        }
        });

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

The whole login completes straight out of the client event with no separate thread synchronization. No Invoke, no Dispatcher.

Then testing started, and not many of the target PCs had .NET Framework 4.5 installed.

So it got rewritten for 4.0 using BackgroundWorker. Miserable. At least it was only a three-screen program.

The lesson: convenient syntax matters less than checking the runtime version on the machines you are deploying to.

Passing LPCTSTR from C# into a C++ DLL

An example of calling a C++ dll from C#.

The C++ side

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

The C# side

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 and [MarshalAs(UnmanagedType.LPWStr)] are the two that matter. Miss either one and the call breaks or the string arrives garbled.

Receiving a string back from C++

Take it as an IntPtr and convert.

1
2
3
4
IntPtr ptr = test("111111");
string data = Marshal.PtrToStringAnsi(ptr);
// always free it
Marshal.FreeHGlobal(data);

Skipping the free leaks.

Saving a WPF control as PNG

I was about to reach for System.Drawing as usual, but WPF already has this built in.

 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 is the key piece — it renders the on-screen control straight to a bitmap.

Summary

  • async/await removes most UI thread synchronization, but check the target runtime first
  • Calling a C++ dll needs CallingConvention.Cdecl plus MarshalAs(UnmanagedType.LPWStr)
  • Always release a returned IntPtr with Marshal.FreeHGlobal
  • Capture a WPF control with RenderTargetBitmap