Freigeben über


Create a WinRT brokered component : Feedback from a “real life” developement

Hi;

Since the Windows 8.1 launch, we can use a new feature, called Brokered Component, to manage legacy components and then writing code for Modern UI application that call desktop .NET objects (pretty cool !).

You can create a Brokered Component project within Visual Studio 2013. You need to install new templates projects, available here : https://visualstudiogallery.msdn.microsoft.com/527286e4-b06a-4234-adde-d313c9c3c23e

For more informations about Brokered Components, just check those articles / videos :

  1. Creating a brokered component from scratch (3 parts) : https://devhawk.net/2014/04/25/brokered-winrt-components-step-one/
  2. A data acces layer within a brokered component : https://blogs.u2u.be/diederik/post/2014/04/25/Building-Enterprise-apps-using-Brokered-Windows-Runtime-Components.aspx
  3. //Build 2014 video presenting the feature : https://channel9.msdn.com/Events/Build/2014/2-515
  4. MSDN documentation : https://msdn.microsoft.com/en-us/library/windows/apps/dn630195.aspx

In this article, I will provide a feedback on a full component; assuming you already know what is a brokered component (I suggest you to check at least the links below)

Here are my observations during the full migration of a legacy component to a brokered component:

Debugging

Yes, you need to debug. Especialy when you create a brokered component (believe me Sourire)

During runtime, no breakpoint will be called in your Brokered Component, since it is hosted by dllhost.exe. By the way, your component is not loaded until you have called your class constructor.

To be able to debug easily, I suggest you to provide a simple constructor in your brokered component. Thus you can add easily a breakpoint in your code.

In a nutshell :

  1. Put a breakpoint after the initialization of your Brokered Component in your modern UI application code.

  2. Put a breakpoint in your Brokered component (In the method you want to debug)

  3. Launch your application and wait for you first breakpoint to be called :

    image21

  4. Attach to the dllhost.exe (Yes, DURING your actual session) : 

    image7[2]

  5. You can now debug your Brokered component Sourire

    image15[2]


Runtime Policy Helper

Yes, often your legacy compoent will be an old component, mainly develop with the FX 2.*

To be able to reuse this component in a .NET 4.* application, you should add a Policy in your configuration file, like this :

 1 <?xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3   <startup useLegacyV2RuntimeActivationPolicy="true">
4     <supportedRuntime version="v4.0"/>
5   </startup>
6 </configuration>

Unfortunately, we can’t add a config file in our Brokered Component. The solution is to be able to configure this legacy runtime Policy with some code... 
Here are two articles on the subject :

  1. https://blogs.msdn.com/b/jomo_fisher/archive/2009/11/17/f-scripting-net-4-0-and-mixed-mode-assemblies.aspx
  2. https://reedcopsey.com/2011/09/15/setting-uselegacyv2runtimeactivationpolicy-at-runtime/

I have integrated this class directly as a private nested class in my Brokered component :

  public sealed class SqlServerReplicationProvider : IReplicationProvider
    {
        private class RuntimePolicyHelper
        {
            private static bool legacyV2RuntimeEnabledSuccessfully;

            public static bool TryEnableLegacyV2Runtime()
            {
                if (legacyV2RuntimeEnabledSuccessfully)
                    return true;

                ICLRRuntimeInfo clrRuntimeInfo = (ICLRRuntimeInfo)RuntimeEnvironment.GetRuntimeInterfaceAsObject(Guid.Empty,
                typeof(ICLRRuntimeInfo).GUID);

                try
                {
                    clrRuntimeInfo.BindAsLegacyV2Runtime();
                    legacyV2RuntimeEnabledSuccessfully = true;
                }
                catch (COMException)
                {
                    legacyV2RuntimeEnabledSuccessfully = false;
                }

                return legacyV2RuntimeEnabledSuccessfully;
            }


            [ComImport]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            [Guid("BD39D1D2-BA2F-486A-89B0-B4B0CB466891")]
            private interface ICLRRuntimeInfo
            {
                void xGetVersionString();
                void xGetRuntimeDirectory();
                void xIsLoaded();
                void xIsLoadable();
                void xLoadErrorString();
                void xLoadLibrary();
                void xGetProcAddress();
                void xGetInterface();
                void xSetDefaultStartupFlags();
                void xGetDefaultStartupFlags();

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void BindAsLegacyV2Runtime();
            }
        }

Then I called the TryEnableLegacyV2Runtime method in my class constructor, pretty straighforward :

 1        public SqlServerReplicationProvider()
2         {
3             // Activer la compatibilité .NET 2.*
4             RuntimePolicyHelper.TryEnableLegacyV2Runtime();
5 
6         }

Abstract Class / Interface

Abstract classes are not available in any Windows Runtime component. Every public class should be sealed and abstract class can’t be sealed…

image33

So you need to use interface, like this :

image32

Enums

Enums works fine in any WinRT project, but they don’t work within a brokered component.

Here is an enum example :

 1     public enum SyncSecurityMode
2     {
3         Standard = 0,
4         Integrated = 1
5     }

Even if I don’t use this enum in my code, It will raise an error during execution. And to be clear, the BadImageFormatException exception won’t help you Sourire 

 

image39

To replace your enum you could:

  1. Use an int. Not very maintenable.
  2. Put the enum in a C++/CX Winmd component. Well … creating a project just to handle an enum is a little bit to complex in my opinion.
  3. Create a sealed object able to reproduce the enum behavior.

Here are some tips I used to create a sealed object to reproduce the enum usage:

  • I have creates the real enum in a private nested enum within my class.
  • I have creates some static methods to be able to create objects like this :
 1 var ssm = SyncSecurityMode.Standard;
  • I have overrided the Equals methods (You can’t override == and != methods in a WinRT component). 
    You need to change the way you use the enum comparaison:
 1 if (this.PublisherSecurityMode.Equals(SyncSecurityMode.Standard))
  • Finally, I have overrides the ToString() method to be able to easily debug my enum class

Here is the full implementation :

  1    public sealed class SyncSecurityMode
 2     {
 3         private enum SyncSecurityModeEnum
 4         {
 5             Standard = 0,
 6             Integrated = 1
 7         }
 8         private SyncSecurityMode(SyncSecurityModeEnum value)
 9         {
10             this.syncSecurityModeEnum = value;
11         }
12         private SyncSecurityMode()
13         {
14         }
15         private SyncSecurityModeEnum syncSecurityModeEnum;
16 
17         public static SyncSecurityMode Standard { get { return new SyncSecurityMode(SyncSecurityModeEnum.Standard); } }
18     
19         public static SyncSecurityMode Integrated { get { return new SyncSecurityMode(SyncSecurityModeEnum.Integrated); } }
20 
21         public override bool Equals(object obj)
22         {
23             if (obj == null) return false;
24 
25             SyncSecurityMode other = (SyncSecurityMode)obj;
26             return this.syncSecurityModeEnum.Equals(other.syncSecurityModeEnum);
27         }
28 
29    }

IDisposable

Every WinRT class should implement IDisposable, especially within a brokered component. During all my tests, each time a class without IDisposable interface ran into an exception (not managed by code) the dllhost.exe wasn’t unload…

I used the IDisposable pattern, where i disposed managed and non-managed ressources :

  1        /// <summary>
 2         /// Dispose pattern
 3         /// </summary>
 4         private bool disposed; // to detect redundant calls
 5 
 6         /// <summary>
 7         /// Closes and dispose.
 8         /// </summary>
 9         public void Close()
10         {
11             Dispose(true);
12             GC.SuppressFinalize(this);
13         }
14 
15         ~SqlServerReplicationProvider()
16         {
17             Dispose(false);
18         }
19         /// <summary>
20         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
21         /// call Close() method
22         /// </summary>
23         public void Dispose()
24         {
25             Close();
26         }
27 
28         protected void Dispose(bool disposing)
29         {
30             if (!disposed)
31             {
32                 if (disposing)
33                 {
34                     // Dispose managed resources.
35                     if (subscriberConn != null && subscriberConn.IsOpen)
36                         subscriberConn.Disconnect();
37 
38                     if (publisherConn != null && publisherConn.IsOpen)
39                         publisherConn.Disconnect();
40                 }
41 
42                 // There are no unmanaged resources to release, but
43                 // if we add them, they need to be released here.
44             }
45             disposed = true;
46         }
47 

More informations available here : https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx

Legacy Objects : handle events

Within FX .NET 2.*, we usually used events in our components. Not a bad idead, except when those events specifie the end of the business logic.

In this particular case, It will be difficult to create an async component (Task) with the IProgress<T> pattern.

Here is a very simple example of one of those kind of component :

  1    public class TimeSource : IDisposable
 2     {
 3         private readonly Timer timer;
 4 
 5         public event EventHandler<DateTime> OnTick;
 6         public event EventHandler<DateTime> Started;
 7         public event EventHandler<DateTime> Ended;
 8 
 9         private int tickCount = 0;
10         private int maxTick = 5;
11         private Double tickDuration = 1000;
12 
13 
14         public DateTime Value { get; set; }
15         public TimeSource()
16         {
17             this.timer = new Timer(tickDuration);
18             this.timer.Elapsed += TimerOnElapsed;
19        }
20 
21         public void Start()
22         {
23             this.timer.Start();
24             Value = DateTime.Now;
25 
26             if (Started != null)
27                 Started(this, Value);
28  
29         }
30 
31         public void Stop()
32         {
33             this.timer.Stop();
34             this.tickCount = 0;
35 
36             Value = DateTime.Now;
37 
38             if (Ended != null)
39                 Ended(this, Value);
40 
41         }
42 
43         private void TimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
44         {
45             try
46             {
47                 Value = DateTime.Now;
48                 
49                 if (tickCount >= maxTick)
50                 {
51                     if (Ended != null)
52                         Ended(this, Value);
53                     
54                     this.timer.Stop();
55                     
56                     tickCount = 0;
57                 }
58                 else
59                 {
60                     if (OnTick != null)
61                         OnTick(this, Value);
62 
63                     tickCount++;
64 
65                 }
66             }
67             catch (Exception ex)
68             {
69                 Console.WriteLine(ex.Message);
70             }
71         }
72 
73         public void Dispose()
74         {
75             this.timer.Stop();
76             this.timer.Elapsed -= TimerOnElapsed;
77             this.timer.Dispose();
78         }
79 
80     }

 

To be simple, TimeSource handle multiples events. Once the Ended event is called, the business logic is ended and the inner timer is stopped.

How to encapsulate those object to be able to create a Task and then use an await ?

Well, we will use TaskCompletionSource<T> . This object will offer us the ability to control the lifetime of our new Task. If you want more information about TaskCompletionSource<T> , here is a very good article by Stephen Toub : https://blogs.msdn.com/b/pfxteam/archive/2009/06/02/9685804.aspx

The most important methods of TaskCompletionSource<T> :

  • SetResult<T> : Set the Task result and return the task
  • SetException<T> : Handle exception
  • SetCanceled : Handle the CancellationToken of the task if provided

Here is the TimeSourceAsync class that encapsulate the TimeSource class :

  1     public class TimeSourceAsync
 2     {
 3 
 4         private TimeSource innerTimeSource = new TimeSource();
 5 
 6         public async Task<DateTime> LaunchAsync(IProgress<DateTime> progress)
 7         {
 8            
 9             return await Task.Run<DateTime>(() =>
10             {
11                 var tcs = new TaskCompletionSource<DateTime>();
12 
13                 innerTimeSource.Started += (s, e) => progress.Report(e);
14                 innerTimeSource.OnTick += (s, e) => progress.Report(e);
15 
16                 innerTimeSource.Ended += (s, e) =>
17                     {
18                         tcs.SetResult(innerTimeSource.Value);
19                     };
20 
21                 innerTimeSource.Start();
22 
23                 return tcs.Task ;
24             });
25         }
26 
27     }

 

Task, AsyncInfo, IAsyncAction, IAsyncOperation, IProgress, CancellationToken …

Obviously, we should implement asynchronous methods within our component.

Creating a Task<T> , like this :

  1         public async Task Sync()
 2         {
 3             return await Task.Run(() =>
 4             {
 5                 SyncStatistics statistics = new SyncStatistics();
 6                 MergeSynchronizationAgent syncAgent = null;
 7 
 8                 // bla bla bla 
 9 
10                 return;
11             });
12         }

Or like this (returning a type) :

  1         public async Task<SyncStatistics> Sync()
 2         {
 3             return await Task.Run<SyncStatistics>(() =>
 4             {
 5                 SyncStatistics statistics = new SyncStatistics();
 6                 MergeSynchronizationAgent syncAgent = null;
 7 
 8                 // bla bla bla 
 9 
10                 return statistics;
11             });
12         }

Or like this, returning a type, and handling CancellationToken and IProgress<T> :

  1         public async Task<SyncStatistics> Sync(CancellationToken cancellationToken, 
 2                                          IProgress<SyncStatistics> progress)
 3         {
 4             return await Task.Run<SyncStatistics>(() =>
 5             {
 6                 SyncStatistics statistics = new SyncStatistics();
 7                 MergeSynchronizationAgent syncAgent = null;
 8 
 9                 // Check if cancellation has occured
10                 if (cancellationToken.IsCancellationRequested)
11                     cancellationToken.ThrowIfCancellationRequested();
12                 
13                 // Report progress
14                 progress.Report(statistics);
15 
16                 // bla bla bla 
17 
18                 return statistics;
19             });
20         }

But ….

image_thumb[2]

To be able to expose such methods, we need to implements some interfaces :

  • IAsyncAction : Handle a simple async method.
  • IAsyncOperation : Handle a simple async method returning a type
  • IAsyncActionWithProgress : Handle a simple async method with progression steps
  • IAsyncOperationWithProgress : Handle a simple async method returning a type with progression steps

 

For more informations about IAsyncAction and IAsyncOperation, you should take a look at those articles, by Stephen Toub :

  1. https://blogs.msdn.com/b/windowsappdev/archive/2012/06/14/exposing-net-tasks-as-winrt-asynchronous-operations.aspx
  2. https://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await.aspx

 

To be clear, you have two solutions to return IAsyncAction or IAsyncOperation :

  1. Use the extensions methods of Task<T> : AsAsyncOperation() ou AsAsyncAction()
  2. Use the AsyncInfo object and its Run() method.

 

Extension methods AsAsyncOperation() ou AsAsyncAction()

Pretty Straightforward. Just call the extension method on your Task<T> and it’s over ! Unfortunately you can’t handle IProgress<T> or CancellationToken :

  1         public IAsyncOperation<SyncStatistics> SynchronizeAsync()
 2         {
 3             return InternalSync(CancellationToken.None, null).AsAsyncOperation();
 4 
 5         }
 6         private Task<SyncStatistics> InternalSync(CancellationToken cancellationToken, IProgress<int> progress)
 7         {
 8             return Task.Run<SyncStatistics>(() =>
 9             {
10                 SyncStatistics statistics = new SyncStatistics();
11                 MergeSynchronizationAgent syncAgent = null;
12 
13                 // Check if cancellation has occured
14                 if (cancellationToken.IsCancellationRequested)
15                     cancellationToken.ThrowIfCancellationRequested();
16 
17                 // Report progress
18                 if (progress != null)
19                     progress.Report(0);
20 
21                 // bla bla bla 
22 
23                 return statistics;
24             });
25         }

 

AsyncInfo.Run()

You have more flexibility within AsyncInfo and its method Run. As you can see, we have 4 static methods with CancellationToken and IProgress behaviors:

  1         public static class AsyncInfo
 2         {
 3             public static IAsyncOperationWithProgress<TResult, TProgress> Run<TResult, TProgress>(
 4                 Func<CancellationToken, IProgress<TProgress>, Task<TResult>> taskProvider);
 5             public static IAsyncActionWithProgress<TProgress> Run<TProgress>(
 6                 Func<CancellationToken, IProgress<TProgress>, Task> taskProvider);
 7             public static IAsyncOperation<TResult> Run<TResult>(
 8                 Func<CancellationToken, Task<TResult>> taskProvider);
 9             public static IAsyncAction Run(
10                 Func<CancellationToken, Task> taskProvider);
11         }
12     }

Here is an example within IAsyncOperationWithProgress<T1, T2> :

 1         public IAsyncOperationWithProgress<SyncStatistics, int> SynchronizeAsync()
2         {
3             return AsyncInfo.Run<SyncStatistics, int>((cancellationToken, progress) =>
4                      InternalSync(cancellationToken, progress));
5         }

Even if it’s not a Task returning from you component, you can still use the await pattern to call your method, like this :

 1         private async void btnSynchronize_Click(object sender, RoutedEventArgs e)
2         {
3            // Constructeur de mon composant WinRT Brokered
4             syncProvider = GetSyncProvider();
5 
6             // Launch Sync
7             var syncCheck = await syncProvider.SynchronizeAsync();
8         }
9 

The main proble is that you don’t have any method available to pass any CancellationToken or IProgress<T> parameters, even if we have correctly implement IAsyncOperationWithProgress<T1,T2>

To be able to pass those parameters, we should use the AsTask() extension method. Again, take a look on this article if you want more informations on the subject : https://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await.aspx

Here is the final code, using the AsTask() method:

  1         private async void btnSynchronize_Click(object sender, RoutedEventArgs e)
 2         {
 3             // Déclaration de ma progression
 4             Progress<int> progress = new Progress<int>((p) => lstEvents.Items.Add(String.Format("{0} % ", p)));
 5            
 6             // Jeton d'annulation
 7             CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
 8             try
 9             {
10                 // Constructeur de mon composant WinRT Brokered
11                 syncProvider = GetSyncProvider();
12       
13                 // Launch Sync
14                 var syncCheck = await syncProvider.SynchronizeAsync().AsTask(cancelTokenSource.Token, progress);
15 
16             }
17             catch (Exception)
18             {
19                 cancelTokenSource.Cancel();
20                 throw;
21             }
22 
23         }

 

 

If you want more performance tips on brokered component, take a look on MSDN : https://msdn.microsoft.com/en-us/library/windows/apps/dn630195.aspx

Especially the paragraph about using struct and returning array of objects instead of List<T>.

Bon développement ! (Hum that’s french … ok, happy coding so ! Sourire )

/Seb