C++ Precon at Tech Ed 2012 – Orlando and Amsterdam

Posted in C++ on February 22nd, 2012 by Admin

At both Tech Ed North America 2012 (June 11th-14th) and Tech Ed Europe 2012 (June 26th-29th) there will be a C++ all day preconference! The title is C++ in 2012: Modern, Readable, Safe, Fast. Our C++ MVP Kate Gregory will deliver it and here’s the abstract:

C++ is gaining momentum as a development language, so whether you’ve never used C++ or stopped using it a decade ago, it may be time to brush up on your skills. With a new standard release providing new keywords and capabilities, C++ is a  featured language for many of the new Microsoft technologies and enables some amazing speed-ups of your application using libraries like PPL and C++ AMP. What’s more, Visual Studio offers tools to native developers that have only been available for managed developers in earlier versions. This all-day session will show you what all the fuss is about and give you the skills you need to understand the advantages of C++ today and how to start applying those benefits to your application.

There are three main categories of attendees who would enjoy this session:

 

  • It’s perfect for those who used C++ a long time ago and need a refresher, a way to catch up with all that has happened to C++ in the last ten years.
  • Developers who have never written a line of C++ code, but are solid in C# or Java will know the basic syntax (if, while, etc) and be able to follow this session and see why there is so much excitement around C++ these days.
  • C++ developers who are not using Visual Studio and not developing for Windows can come and see how their C++ skills will translate to these technologies and how quickly they can be productive on a new platform.

This preconference is technical and has lots of code in it. It’s not an “introduction to C++” that starts from scratch. It’s about providing the inspiration you need to start using particular features and starting writing modern C++. While you’re at TechEd, you can also attend some C++ breakout sessions to go deeper into particular technologies and tools that caught your interest. If you watched the sessions from the GoingNative 2012 conference, and could follow them, you know how much C++ programming has changed this century. If not, and you’re curious what the buzz is about, come and find out! It’s a great way to start your TechEd experience, June 10th in Orlando or June 25th in Amsterdam. We’ll see you there!


Visual C++ Team Blog

Tags: , , , ,

Reducing the Size of Statically-linked MFC Applications in VC11

Posted in C++ on February 14th, 2012 by Admin

Pat Brenner

Hello, I’m Pat Brenner, a developer on the Visual C++ Libraries team, and I am the primary developer working on the Microsoft Foundation Classes (MFC).

In Visual Studio 2010, the size of statically-linked MFC applications grew substantially. We’ve gotten a number of comments about this issue, so I wanted to post an article about the cause and the solution that we have come up with.

Cause

In Visual Studio 2010, we added a feature to the resource editor which allows you to add MFC controls to your dialogs. The MFC control types appear in the toolbox along with the standard Windows controls. Properties specific to the MFC controls can be set on them, so they behave as desired when the dialog is created.

In order for this to work properly, a DLGINIT block has to be written in the RC file for the project, which contains the properties information in binary format. The DLGINIT block has to be parsed when the dialog is being initialized, so the controls can be initialized using the information in the DLGINIT block. The code to do this parsing lives in CWnd::ExecuteDlgInit. ExecuteDlgInit method lives in WINCORE.CPP, whose object is always included in every statically-linked MFC application (because it contains the CWnd constructors and the AfxWndProc method).

The code that performs the MFC control initialization, of course, needs to know about all of the MFC controls. Those controls, in turn, may need to know about various visual managers in order to know how to draw themselves. And the visual managers, in turn, have dependencies on other MFC classes.

The result of these dependencies is that much more of MFC needs to be pulled into a statically-linked MFC application, because the linker cannot determine at build time that none of those methods will need to be called, since it all depends on the content of the RC file and DLGINIT structures inside it.

We were alerted to this size increase in statically-linked MFC applications shortly before the release of Visual Studio 2010 RTM, but we were not able to definitively establish the cause before Visual Studio 2010 shipped. Even if we had, we most likely would not have been able to put the finishing touches on a solution before the release date, because we had to try several different approaches before arriving at a working solution that puts a very small requirement on the MFC developer.

Solution

To fix the problem, we eliminated a number of dependencies between MFC classes (further details are below). We also moved several methods that have an effect on the MFC control initialization:

  • CWnd::ExecuteDlgInit, DDX_Control, AfxRegisterMFCCtrlClasses
  • CMFCControlContainer::SubclassDlgControls and CMFCControlContainer::PreUnsubclassControl

into separate source modules.

These separate source modules are then compiled in two different ways:

  • With _AFX_NO_MFC_CONTROLS_IN_DIALOGS not #defined, they are built into the standard static MFC libraries, NAFXCW[d].LIB and UAFXCW[d].LIB, with the standard behavior enabled.
  • With _AFX_NO_MFC_CONTROLS_IN_DIALOGS #defined, they are built into a new small static MFC library, AFXNMCD[d].LIB, without the ability to initialize MFC controls on dialogs. (The NMCD in the library name is an acronym for “No MFC Controls on Dialogs”.)

The new smaller library has the same methods (same names, but different implementations) as the larger standard MFC libraries, so we must make sure to link it in first. This ensures that the functions that don’t have any dependency on MFC control initialization are used and the dependencies are eliminated. This is accomplished via symbols that are defined in the new source modules, and force-included via #pragma statements in AFX.H based on #defines set.

The result of this work is that you can simply #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS in your MFC application’s stdafx.h file, and all the code that performs MFC control initialization on dialogs will be left out of your application. In a simple dialog-based application, this will reduce the size of the application by approximately 80%. [Note that if you do use MFC controls on a dialog, and build with _AFX_NO_MFC_CONTROLS_IN_DIALOGS #defined, your application may not run at all (or dialogs will not appear) because a dialog containing a nonexistent window class cannot be created. We added TRACE statements to MFC to this effect to help point out this issue.]

In addition, we have made changes in the code generated by the MFC application wizard. It will generate code that contains #ifdefs for the _AFX_NO_MFC_CONTROLS_IN_DIALOGS, so:

  • Dialogs will be derived from CDialog instead of CDialogEx if the #define is set.
  • No CShellManager will be created in the application’s InitInstance method if the #define is set.

We have implemented these changes in MFC for the next major release of Visual Studio. Now that we understand the cause and the best solution, we looked at the possibility of porting the changes back to Visual Studio 2010 in order to benefit applications built with that version. Unfortunately, the changes we made to reduce the dependencies between MFC classes included:

  1. Moving D2D-related member functions/data out of the _AFX_GLOBAL_DATA class to a separate class
  2. Adding a new virtual method to both CMDIChildWnd and CMDIChildWndEx
  3. Adding a new method to the CWinApp class

Because these changes introduce binary incompatibilities, we are not able to port the changes back to Visual Studio 2010 without breaking existing MFC applications.

I hope you find this information helpful!

 

 

Pat Brenner
Visual C++ Libraries Development


Visual C++ Team Blog

Tags: , , , ,

GoingNative 2012 Online

Posted in C++ on February 11th, 2012 by Admin

GoingNative 2012Thanks to everyone who came to Redmond and/or watched online to participate in Going Native 2012, last week’s global C++-fest. It was a lot of fun, and generated a lot of useful and important talks that we hope will help continue disseminate understanding of C++11 throughout the global C++ community.

All the videos and decks are available online for on-demand viewing or download.


Visual C++ Team Blog

Tags: , ,

The Microsoft C++ Compiler Turns 20!

Posted in C++ on February 4th, 2012 by Admin

Microsoft C/C++ 7.0

This month, we enter the third decade of C++ at Microsoft.

It was twenty years ago, in February of 1992, that we released our first C++ compiler: Microsoft C/C++ 7.0. Before then, we already worked with several of the C++ “preprocessor” compilers that took C++ and converted it to C before our compiler then created the executable program. But starting in 1992, Microsoft’s premier native compiler supported C++ directly, and has done so ever since.

C/C++ 7.0 shipped in a box that was over two feet long and produced MS-DOS, Windows and OS/2 applications. It also sported the last of the character oriented development environments for C that we ever shipped – the following product was Visual C++, which built on what we had learned from delivering QuickC. Since those early days, we have shipped eleven major releases of C/C++ products (ignoring small point upgrades) for both Windows and embedded development.

This month, on the 20th anniversary of our first C++ compiler, we’re looking forward to shipping the beta of Visual C++ 11. It includes support for ARM processors, Windows 8 tablet apps, C++ AMP for heterogeneous parallel computing, automatic parallelization, and the complete ISO C++11 standard library… and a few more of the new C++11 language features too.

Last summer, we pledged to publish the C++ AMP specification as an open specification that any compiler vendor may implement, to target any operating system platform. Today, we published the C++ AMP open specification to support using C++ for heterogeneous parallel computing on GPUs and multicore/SSE today, with more to come in the future. Read the full announcement and download the specification at the Native Concurrency blog.

Finally, to make this anniversary celebration complete, we’re shifting gears to pick up speed: After Visual C++ 11 ships, you’ll see us deliver compiler and library features more frequently in shorter out-of-band release cycles than our historical 2- or 3-year timeframe. And, of course, the first and most important target of those more agile releases is to deliver more and more of the incredible value in the new ISO Standard C++11 language. Please check Herb Sutter’s keynote at GoingNative 2012 for further details.

After 20 years, C++ is alive and well, and going stronger and faster than ever, not just at Microsoft but across our industry. Use it. Love it. And go native!


Visual C++ Team Blog

Tags: , ,

The Magic of Going Native 2012 Starts Today!

Posted in C++ on February 3rd, 2012 by Admin

UPDATE: We are already streaming live!!

c7271438-ddbd-4e22-afbb-f500cc84afa2[1]

Are you guys ready? This is the agenda for this event happening during today and tomorrow.

Day 1 – C++11 Today

Day 2 – C++11 Today and Tomorrow

 

The event will be available for live streaming and on demand!! We’ll see you then.


Visual C++ Team Blog

Tags: , , , , ,

Enhancements to /GS in Visual Studio 11

Posted in C++ on February 2nd, 2012 by Admin

Tim Burrell outlines more of the work done by Security Science & the Visual Studio team.

He previously noted that they are updating the on-by-default /GS compiler switch, which provides protection against some memory safety bugs such as buffer overflows. In a new post he provides additional information on those changes.

[Go to the article]


Visual C++ Team Blog

Tags: , ,

Array-like access and Iterators for Homogeneous Tuples

Posted in C++ on January 26th, 2012 by Admin

Question often comes up whether tuples can have traditional iterators? In general, the answer is: No! They cannot. That’s because tuples typically contain different types and traditional iterators, which are modeled after pointers, can not dereference to objects of multiple types. However, homogeneous tuples can have iterators. So I thought it would be a fun exercise to write one. I wonder why one would use a homogeneous tuples instead of just plain arrays. But lets do it anyways because we can.

Although this exercise sounds rather naive and unnecessary, I stumbled upon two very interesting topics along the way.

  • A need for new iterator concepts to separate the notions of element access from iterator traversal. Yes! iterators for homogeneous tuples can’t be modeled accurately using conventional iterator categories. Don’t believe me? Please read on…
  • How inherited constructors may be simulated on compilers that don’t support them today.

From this point onward a tuple is assumed to be a homogeneous tuple. First thing we need is a way of accessing tuple’s contents using a run-time integer instead of a compile-time integer. We need an adapter that uses a run-time integer index to return the n-th element in a homogeneous tuple. If n is larger than the bounds of the tuple, the adapter will throw a std::out_of_range exception.

template <typename Tuple,        size_t I = std::tuple_size<Tuple>::value-1>class TupleAt{   typedef typename std::tuple_element<0, Tuple>::type T;

 public:   static T & get(Tuple & tuple, size_t index)   {     return (index == I)? std::get<I>(tuple) : TupleAt<Tuple, I-1>::get(tuple, index);   }};

template <typename Tuple>class TupleAt<Tuple, 0>{   typedef typename std::tuple_element<0, Tuple>::type T; public:

   static T & get(Tuple & tuple, size_t index)   {     if(index == 0)       return std::get<0>(tuple);     else       throw std::out_of_range("Tuple iterator dereferenced out of valid range.");   }};

The TupleAt template takes the type of the tuple and its size as template parameters. It assumes that the type of the first element is also the type of the rest of the elements in the tuple (i.e. a homogeneous tuple). TupleAt::get function returns a reference to the n-th element in the tuple. It does that using repeated comparisons from the size of the tuple (std::tuple_size<Tuple>::value) down to zero. TupleAt is specialized for zero so that the recursion ends. If the index does not fall in the expected range, std::out_of_range exception is thrown.

Note that this access pattern is linear in complexity. For a tuple of N elements, it may take up to N comparisons to return the right element.

Array-like access to Homogeneous Tuples

I created a tuple_array class to provide array-like access to the elements of the tuple. It uses TupleAt internally.

template <typename... T>class tuple_array : public std::tuple<T...>{   typedef std::tuple<T...> Tuple;   typedef typename std::tuple_element<0, Tuple>::type HeadType;   enum { TUPLE_SIZE = std::tuple_size<tuple>::value };

   HeadType * ref_;   size_t last_;

 public:   USING(tuple_array, Tuple)   {     ref_ = & TupleAt<tuple>::get(*this, TUPLE_SIZE-1);     last_ = TUPLE_SIZE-1;   }

   HeadType & operator [] (size_t index)   {     if(last_ != index)     {       ref_ = & TupleAt<tuple>::get(*this, index);       last_ = index;     }     return *ref_;   }};

Class tuple_array inherits from std::tuple and just provides operator [] function. It always returns a reference to HeadType typedef, which is the type of the first element in the tuple. To improve efficiency, the tuple_array class caches a pointer to the last dereferenced index in the tuple. std::tuple has a zillion constructors to create a tuple. To avoid repeating the constructors in the derived tuple_array class, I wanted to use inherited constructors. However, g++ 4.7 does not support it at this moment. So I’m using a variadic template constructor to mimic the behavior of inherited constructors.

#define USING(Dervied, Base)                                       \     template<typename ...Args,                                   \            typename = typename std::enable_if                    \            <                                                     \               std::is_constructible<Base, Args...>::value        \            >::type>                                              \   Dervied(Args &&...args)                                        \       : Base(std::forward<Args>(args)...)                        \

The inherited constructor trick is captured in a macro, which I stole shamelessly from here. The USING macro defines a variadic template constructor and forwards all the arguments to the underlying base constructor. To avoid being overly greedy, it enables instantiation only if the base is constructible from the given parameters. std::is_constructible<Base, Args…>::value provides a neat way of checking that at compile-time.

Finally, we need a simple function to create the tuple_array. Function make_tuple_array is a factory function to create tuple_arrays from a list of arguments. Note how it uses the uniform initialization syntax without specifying the actual type. Using make_tuple_array is just like an array. However, note that element access is linear and not constant-time.

template <typename... T>tuple_array<T...> make_tuple_array(T... args){ return { std::forward< T&& >(args)... };}

int main(void){ auto ta = make_tuple_array(20, 30, 40); printf("%d %d %d", ta[0], ta[1], ta[2]); // prints 20 30 40}

Iterators for Homogeneous Tuples

Now lets turn our attention to iterators.

What category would an iterator for homogeneous tuple belong? Random access? Bidirectional? It appears to me that the homogeneous tuple iterator could simply use an internal index to remember what position it is at and use the TupleAt::get to return the element when dereferenced. Arbitrary arithmetic can be performed in constant-time on the internal index to move the iterator. This indicates that the iterator is a random access iterator.

However, the dereference function is not constant-time as discussed earlier. As a result, it is not a random access iterator. Clearly, traversal is random access but element access is not. Existing iterator categories do not support this distinction. The standard random access iterator [5] requires all operations to be amortized constant time.

What we really need is a way to distinguish between the categories of element access and the categories of traversal. This is precisely the point of new iterator concepts in boost.

For now, we’ll just consider that the iterator for homogeneous tuple is a random access iterator. Here is how it looks like with a lot of boilerplate overloaded operators.

template <typename Tuple>class tuple_iterator   : public std::iterator<std::random_access_iterator_tag,                         typename std::tuple_element<0, Tuple>::type>{   typedef typename std::tuple_element<0, Tuple>::type T;   enum { TUPLE_SIZE = std::tuple_size<Tuple>::value };

   Tuple * tuple;   int current_;   int last_;   T * ref_;

   T * update_ref()   {     if(current_ != last_)     {       ref_ = & TupleAt<Tuple>::get(*tuple, current_);       last_ = current_;     }     return ref_;   }

public:

   typedef int difference_type;

   explicit tuple_iterator(Tuple & t, int i = TUPLE_SIZE)     : tuple(&t),       current_(i),       last_(i-1),       ref_(&TupleAt<Tuple>::get(*tuple, last_))   {}   T & operator *() {     return *update_ref();   }   T * operator ->() {     return update_ref();   }   T & operator [] (int offset) {     return TupleAt<Tuple>::get(*tuple, current_+offset);   }   tuple_iterator & operator ++ () {     if(current_ < TUPLE_SIZE)       ++current_;     return *this;   }   tuple_iterator operator ++ (int) {     tuple_iterator temp(*this);     ++(*this);     return temp;   }   tuple_iterator & operator -- () {     if(current_ >= 0)       --current_;     return *this;   }   tuple_iterator operator -- (int) {     tuple_iterator temp(*this);     --(*this);     return temp;   }   tuple_iterator operator - (int i) const {     tuple_iterator temp(*tuple, current_-i);     return temp;   }   tuple_iterator & operator -= (int i) {     current_-=i;     return *this;   }   tuple_iterator operator + (int i) const {     tuple_iterator temp(*tuple, current_+i);     return temp;   }   tuple_iterator & operator += (int i) {     current_+=i;     return *this;   }   difference_type operator - (const tuple_iterator & ti) const {     return current_ - ti.current_;   }   bool operator < (const tuple_iterator &ti) const {     return index < ti.index;   }   bool operator > (const tuple_iterator &ti) const {     return index > ti.index;   }   bool operator <= (const tuple_iterator &ti) const {     return index <= ti.index;   }   bool operator >= (const tuple_iterator &ti) const {     return index >= ti.index;   }   bool operator == (tuple_iterator const & ti) const {     return (tuple == ti.tuple) && (index == ti.index);   }   bool operator != (tuple_iterator const & ti) const {     return !(*this == ti);   }};

template <>class tuple_iterator <std::tuple<>>{ public:   tuple_iterator(std::tuple<>, size_t i = 0) {}};

The tuple_iterator class provides the usual typedefs (e.g., difference_type, value_type, pointer, reference, and iterator_category) and overloaded operators (e.g., *, ->, [], +, -, +=, -=, -, +, <, >, <=, >=, ==, !=) to support the requirements of random access iterator. Just like tuple_array class it caches a pointer to the last element that was dereferenced. It goes through O(N) comparisons only if the tuple iterator is dereferenced at a different index than what is cached. A specialization of tuple_iterator for empty tuple is also provided. It has no members other than a constructor because there is nothing to dereference to!

Finally, we need a way to create the begin and end iterator from a non-empty tuple. We add the corresponding functions.

template <typename... Args>tuple_iterator <std::tuple<Args...>> begin(std::tuple <Args...> &t){ return tuple_iterator <std::tuple<Args...>>(t, 0);}

template <typename... Args>tuple_iterator <std::tuple<Args...>> end(std::tuple <Args...> &t){ return tuple_iterator <std::tuple<Args...>>(t);}

If no index is passed to the iterator constructor, it points to the end of the tuple. The internal index of such an iterator is same as the size of the tuple. An iterator at the beginning has index = 0 — the first element. Using the iterators is now straightforward. I do not discuss constant and reverse iterators here.

int main(void){ auto tuple = std::make_tuple(10, 20, 30, 40); auto ta = make_tuple_array(4, 2, 1);

 std::copy(begin(tuple), end(tuple), std::ostream_iterator<int>(std::cout, " ")); std::sort(begin(ta), end(ta));

 for(int i : ta) {   std::cout << i << std::endl; }

 return 0;}

I think, this rather naive exercise turned out to be quite interesting. Hopefully, you enjoyed as much as I did.


C++ Truths

Tags: , , , ,

GoingNative 2012 Full Schedule

Posted in C++ on January 24th, 2012 by Admin

Charles has recently published the agenda for GoingNative 2012 –the first C++ only event done in MS in many years.

Great speakers and compelling topics. Take a look here.


Visual C++ Team Blog

Tags: , , ,

General-purpose Automatic Memoization for Recursive Functions in C++11

Posted in C++ on January 14th, 2012 by Admin

Memoization is a widely known optimization technique used primarily to speed up computer programs by having function calls avoid repeating the calculation of results for previously processed inputs. Repeated calculations are avoided by reusing previously computed results, which must be cached such that look-up is faster than recomputing.

Consider a simple fibonacci program

unsigned long fibonacci(unsigned n){  return (n < 2) ? n :  fibonacci(n - 1) + fibonacci(n - 2);}

This algorithm is a frustratingly slow way of computing the Nth fibonacci number (N starting at 0). It does a lot of redundant recomputations. But the beauty of this program is that it is really simple. To speed it up without changing its logic significantly, we could use memoization.

Using some clever C++11 techniques, it is possible to memoize this function, which looks like below.

unsigned long fibonacci(unsigned n){  return (n < 2) ? n :       memoized_recursion(fibonacci)(n - 1) +       memoized_recursion(fibonacci)(n - 2);} 

To solve this problem, I took inspiration from this post on automatic memoization. I’ll go in lot more detail here including recursive functions and memory management. Here we go!

The memoize function I’m using is slightly different than that of the post above.

template <typename ReturnType, typename... Args>std::function<ReturnType (Args...)>memoize(ReturnType (*func) (Args...)){  auto cache = std::make_shared<std::map<std::tuple<Args...>, ReturnType>>();  return ([=](Args... args) mutable  {          std::tuple<Args...> t(args...);          if (cache->find(t) == cache->end())              (*cache)[t] = func(args...);          return (*cache)[t];  });}

Function memoize accepts a pointer to a free function, wraps it in a lambda, and turns the lambda into a std::function. Returning a a std::function is a common C++11 idiom of returning a lambda from a function that creates it. The implementation of the lambda is pretty straight forward if you are familiar with C++11 variadic templates. It creates a tuple of arguments and checks if it exists in the cache. In that case, the stored result is returned instead of recomputing it. The cache used for mapping arguments to the return value, is allocated dynamically. A std::shared_ptr manages the memory. The lambda copies the std::shared_ptr by value. As long as there is at least one std::function alive, the cache will remain intact.

Memoized functions may be called from different places. It is quite cumbersome to pass the memoized function everywhere it is called. There should be a way to look up a memoized version of the function without loosing the state. So our next step is to make the same memoized function available from anywhere in the program. We need a map of function pointer to a memorized std::function. Specifically, we need a std::unordered_map for fast lookup.

template <typename F_ret, typename...  F_args>std::function<F_ret (F_args...)>memoized_recursion(F_ret (*func)(F_args...)){  typedef std::function<F_ret (F_args...)> FunctionType;  static std::unordered_map<decltype(func), FunctionType> functor_map;

  if(functor_map.find(func) == functor_map.end())    functor_map[func] = memoize(func);

  return functor_map[func];}

Here I introduce “memoized_recursion” function that our recursive fibonacci function is calling. It has a static std::unordered_map. It simply looks up the memorized std::function based on the function pointer value. If it does not find one, it creates it and stores it for subsequent access. Function pointers are unique; so there are no collisions possible. Here is how to call it.

memorized_recursion(fibonacci)(10);

The solution is not finished yet though. Memoization obviously builds up state very fast. If many functions are memoized with a large number of parameters, the state grows explosively. There must be some way to reclaim the memory.

Remember that the memoized state grows in the lambda. The dynamically allocated map stores the cache for corresponding function. We need to access the object that is hidden inside a lambda. Lambda has a compiler-defined type and only thing you can do with it is call it. So how would we clear the cache it is building up?

The answer is surprisingly simple! Just assign the memoizer (the lambda) with another default initialized memoizer.

We already have memoize function, which returns a default initialized memoizer. We simply assign the new one in place of the old one. Here’s how the new memoized_recursion looks like

enum class Cache : unsigned int { NO_RECLAIM, RECLAIM };

template <typename F_ret, typename... F_args>std::function<F_ret (F_args...)>memoized_recursion(F_ret (*func)(F_args...), Cache c = Cache::NO_RECLAIM){  typedef std::function<F_ret (F_args...)> FunctionType;  static std::unordered_map<decltype(func), FunctionType> functor_map;

  if(Cache::RECLAIM == c)    return functor_map[func] = memoize(func);

  if(functor_map.find(func) == functor_map.end())    functor_map[func] = memoize(func);

  return functor_map[func];}

I’m using strongly typed enums to pass programmer’s intent to clear the cache. Here is how you call it.

memoized_recursion(fibonacci, Cache::RECLAIM);

That’s all for now. I hope you enjoyed it.


C++ Truths

Tags: , , , , ,

Try It Now: Use PPL to Produce Windows 8 Asynchronous Operations

Posted in C++ on December 11th, 2011 by Admin

There’s a new revision of the Concurrency Runtime and Parallel Pattern Library sample pack that demonstrates a convenient way of consuming and producing Windows Runtime asynchronous operations using PPL.

Read the announcement at sister blog Parallel Programming in Native Code.


Visual C++ Team Blog

Tags: , , ,