Understanding Classic COM Interoperability w/.NET Apps

Environment: .NET Framework SDK Beta 1, Visual C++ 6, SP4

Disclaimer

The information in this article & source code are published in accordance with the Beta 1 bits of the .NET framework SDK.

Ever wondered how all those COM components that we have written through the years play along with the .NET runtime. If you are a diehard COM developer interested in knowing how Classic COM Components (Yikes !.It does hurt to see COM being called Classic COM) are positioned in the .NET world, read on.

Introduction
Getting Started
Generating metadata from the COM Typelibrary
Binding to & Invoking our COM component from a .NET Application
Accessing other supported interfaces and Dynamic Type Discovery
Late Binding to COM Objects
Understanding COM Threading models & Apartments from a .NET application's perspective
COM's position in the .NET world.

Introduction :

After playing around with the .NET Technology Preview & recently the .NET Beta 1 bits , there is no doubt in most developers' mind that the .NET technology is a powerful way to build components and distributed systems for the enterprise. But then, what about the tons of existing reusable COM components that you've built through the last few years, not to mention all those cups of coffee & sleepless nights. Is it the end of all those components in the .NET world ? . Will those components work hand-in-hand with the .NET managed runtime ? . For all those of us who program COM for a living , and for those who live by the 'COM is love' mantra, there is good news. COM is here to stay and .NET framework managed applications can leverage existing COM components. Certainly, Microsoft wouldn't want to force companies to abandon all their existing components, especially components that were written in one of the most widely used object model for developing both desktop & distributed applications. Classic COM components interoperate with the .NET runtime through an interop layer that will handle all the plumbing between translating messages that pass back and forth between the managed runtime and the COM components operating in the unmanaged realm, and vice versa. In this article, we will focus on how you can get COM components to work with the .NET managed runtime.

Getting Started :

So let's get started right away. Let's write a simple COM component using ATL that gives us the arrival details for a specific airline. For simplicity, we always return details for only the 'Air Scooby IC 5678' airline and return an error for any other airline. That way, you can also take a look at how the error raised by the COM component can be propagated back so that it can be caught by the calling .NET client application.

Here's the IDL definition for the IAirlineInfo interface:

interface IAirlineInfo : IDispatch
{
 [id(1), helpstring("method GetAirlineTiming")] 
 HRESULT GetAirlineTiming([in] BSTR bstrAirline, 
                          [out,retval] BSTR* pBstrDetails);

 [propget, id(2), helpstring("property 
 LocalTimeAtOrlando")] HRESULT 
 LocalTimeAtOrlando([out, retval] BSTR 
 *pVal);
};

And here's the implementation of the GetAirlineDetails method:

STDMETHODIMP CAirlineInfo::GetAirlineTiming(BSTR 
	bstrAirline, BSTR *pBstrDetails)
{
	_bstr_t bstrQueryAirline(bstrAirline);
	if(NULL == pBstrDetails) return E_POINTER;
	
	if(_bstr_t("Air Scooby IC 5678") == 
		bstrQueryAirline)
	{
		// Return the timing for this Airline
		*pBstrDetails = _bstr_t(_T("16:45:00 - Will 
			arrive at Terminal 3")).copy();
	}//if
	else
	{
		// Return an error message 
		return Error(LPCTSTR(_T("Airline Timings 
			not available for this Airline" )),  
			__uuidof(AirlineInfo), 
			AIRLINE_NOT_FOUND);
	}
	return S_OK;
}

So now, since we are ready with our component, let's take a look at generating some metadata from the component's type library so that the .NET client can use this metadata to talk to our component and invoke it's methods.

Generating metadata from the COM Typelibrary :

COM Interop illustration
Figure 1: How the COM interop works

A .NET application that needs to talk to our COM component cannot directly consume the functionality that's exposed by it. So we need to generate some metadata. This metadata layer is used by the runtime to ferret out type information, so that it can use this type information at runtime to manufacture what is called as a Runtime Callable Wrapper (RCW). The RCW handles the actual activation of the COM object and handles the marshalling requirements when the .NET application interacts with it .The RCW also does tons of other chores like managing object identity, object lifetime, and interface caching. Object lifetime management is a very critical issue here because the .NET runtime moves objects around and garbage collects them. The RCW serves the purpose of giving the .NET application the notion that it is interacting with a managed .NET component and it gives the COM component in the unmanaged space, the impression that it 's being called by a good old COM client. The RCW's creation & behavior varies depending on whether you are early binding or late binding to the COM object. Under the hood, the RCW is doing all the hard work and thunking down all the method invocations into corresponding vtable calls into the COM component that lives in the unmanaged world. It's an ambassador of goodwill between the managed world and the unmanaged IUnknown world.

So let's generate the metadata wrapper for our Airline COM component. To do that, we need to use a tool called the TLBIMP.exe. The Type library Importer (TLBIMP ) ships with the .NET SDK and can be found under the Bin subfolder of your SDK installation. The Typelibrary Importer utility reads a typelibrary and generates the corresponding metadata wrapper containing type information that the .NET runtime can comprehend.

From the DOS command line, type the following command :
TLBIMP AirlineInformation.tlb /out:AirlineMetadata.dll

This command tells the TLBIMP to read your AirlineInfo COM typelibrary and generate a corresponding metadata wrapper called AirlineMetadata.dll. If everything went off well, you should see a message such as the following :
TypeLib imported successfully to AirlineMetadata.dll

So what kind of type information does this generated metadata contain and how does it look like. As COM folks, we have always loved our beloved OleView.exe, at times when we felt we needed to take a peek at a typelibrary's contents, or for the tons of other things that OleView is capable of doing. Fortunately, the .NET SDK ships with a disassembler called ILDASM that allows us to view the metadata & the Intermediate language (IL) code generated for managed assemblies. Every managed assembly contains self-describing metadata and ILDASM is a very useful tool when you need to peek at that metadata. So go ahead and open AirlineMetadata.dll using ILDASM. Take a look at the metadata generated and you see that the GetAirlineTiming method is listed as a public member for the AirlineInfo class. There is also a constructor that gets generated for the AirlineInfo class. The method parameters also have been substituted to take their equivalent .NET counterparts. In our example the BSTR has been replaced by the System.String parameter. Also notice that the parameter that was marked [out,retval] in the GetAirlineTiming method was converted to the actual return value of the method (returned as System.String ). Any failure HRESULT values that are returned back from the COM object (in case of an error or failed business logic) are thrown back as exceptions.

ILDASM view of metadata
Figure 2 : IL Disassembler - a great tool for viewing metadata and MSIL for managed assemblies

Binding to & Invoking our COM component from a .NET Application:

Now that we have generated the metadata that's required by a .NET client, let's try invoking the GetAirlineTiming method in our COM object from the .NET Client. So here's a simple C# client application that creates the COM object using the metadata that we generated earlier and invokes the GetAirlineTiming method.

String strAirline = "Air Scooby IC 5678";
String strFoodJunkieAirline = "Air Jughead TX 1234";
try
{
 AirlineInfo objAirlineInfo;
 objAirlineInfo = new AirlineInfo();

 // Call the GetAirlineTiming() method
 System.Console.WriteLine("Details for Airline {0} --> {1}", 
 strAirline,objAirlineInfo.GetAirlineTiming(strAirline));

 // This should make the COM object throw us an exception	
 System.Console.WriteLine("Details for Airline {0} --> {1}", 
 strFoodJunkieAirline,objAirlineInfo.GetAirlineTiming(strFoodJunkieAirline)); 
}//try
catch(COMException e)
{
 System.Console.WriteLine("Oops- We encountered an error. The Error message is : {0}. 
                          The Error code is {1}",e.Message,e.ErrorCode);
}//catch

Under the hood, the runtime fabricates an RCW and this maps the metadata class methods and fields to methods and properties exposed by the interface that the COM object implements. One RCW instance is created for each instance of the COM object. The .NET runtime only cares about managing the lifetime of the RCW and garbage collects the RCW. It's the RCW that takes care of maintaining reference counts on the COM object that it's mapped to, thereby, shielding the .NET runtime from managing the reference counts on the actual COM object. As shown in the ILDASM view, the AirlineInfo metadata is defined under a namespace called AIRLINEINFORMATIONLib. The .NET client sees all the interface methods as if they were class members of the AirlineInfo class. All we need to do is, just create an instance of the AirlineInfo class using the new operator and call the public class methods of the created object. When the method is invoked, the RCW thunks down the call to the corresponding COM method call. The RCW also handles all the marshalling & object lifetime issues. To the .NET client it looks nothing more than it's actually creating a managed object and calling one of it's public class members. Anytime the COM method raises an error, the COM error is trapped by the RCW, and the error is converted into an equivalent COMException class (found in the System.Runtime.InteropServices namespace). Of course, the COM object still needs to implement the ISupportErrorInfo interface for this error propagation to work, so that the RCW knows that your object provides extended error information. The error can be caught by the .NET client by the usual try-catch exception handling mechanism and has access to the actual error number, description, the source of the exception and other details that would have been available to any COM aware client.

Accessing other supported interfaces and Dynamic Type Discovery:

So how does the classic QueryInterface scenario work from the perspective of the .NET client when it wants to access another interface implemented by the COM object. To QI for another interface, all you need to do is cast the current object to the other interface that you need, and voila, your QI is done. You are now ready to invoke all the methods/properties of the other interface. It's that simple. Again, the RCW does the all the hard work under the covers. It's a lot like how the VB runtime shields us from having to write any explicit QueryInterface related code and simply does the QI for you when you set one object type to an object of another associated type. In our example, suppose you wanted to call the methods on the IAirportFacilities interface which is another interface implemented by our COM object, you then simply cast the AirlineInfo object to the IAirportFacilities interface. You can now call all the methods that are a part of the IAirportFacilities interface. But before performing the cast, you may want to check if the object instance that you are currently holding supports or implements the interface type that you are querying for. You can do this by using the IsInstanceOf method in the System.Type class. If it returns TRUE, then you know that the QI succeeded. You can then safely perform the cast. In case you cast the object to some arbitrary interface that the object does not support, a System.InvalidCastException exception is thrown. This way the RCW ensures that you are casting to only interfaces that are implemented by the COM object and not just any arbitrary interface type.

try
{
 AirlineInfo objAirlineInfo; 
 IAirportFacilitiesInfo objIFacilitiesInfo; 

 // Create a new object 
 objAirlineInfo = new AirlineInfo(); 

 // Check if the object implements the 
 // IAirportFacilitiesInfo interface 
 if(objIFacilitiesInfo.GetType().IsInstanceOf(objAirLineInfo)) 
 { 
  // Peform the cast 
  objIFacilitiesInfo = (IAirportFacilitiesInfo)objAirlineInfo; 

  // Call the method of the other interface 
  System.Console.WriteLine("{0}",objIFacilitiesInfo.GetInternetCafeLocations()); 
 }//if

 ISomeInterface objISomeJunk;
 //Will throw an InvalidCastException 
 objISomeJunk = (ISomeInterface) objAirlineInfo;   

}//try
catch(InvalidCastException eCast)
{
 System.Console.WriteLine("We got an Invalid Cast Exception - Message is {0}", 
                          eCast.Message);   

}//catch

Late Binding to COM Objects :

All the examples that you saw above used the RCW metadata to early bind the .NET Client to the COM object. Though early binding provides a whole smorgasbord of benefits like strong type checking at compile time, providing auto-completion capabilities from type-information for development tools, and of course, better performance, there may be instances when you really need to late bind to a Classic COM object when you don't have the compile time metadata for the COM object that you are binding to. You can achieve late binding to a COM object through a mechanism called Reflection. This does not apply to COM objects alone. Even .NET managed objects can be late bound using Reflection. Also, if your object contains a pure dispinterface only, then you are pretty much limited to only using Reflection to activate your object and invoke methods on the interface. For late binding to a COM object, you need to know the object's ProgID. The CreateInstance static method of the System.Activator class, allows you to specify the Type information for a specific class and it will automatically create an object of that specific type. But what we really have is a ProgID and not true .NET Type Information. So we need to get the Type Information from the ProgID for which we use the GetTypeFromProgID method of the System.Type class. The System.Type class is one of the core enablers for Reflection. So now that you have created an object instance, you can invoke any of the methods/properties supported by the object's default interface using the System.Type::InvokeMember method of the Type object that you got back from GetTypeFromProgID. All we need to know is the name of the method or property and the kind of parameters that the method call accepts. The parameters are bundled up in a generic System.Object array and passed away to the method.You would also need to set the appropriate binding flags depending on whether you are invoking a method or getting/setting the value of a property. That's all there is to late binding to a COM object.

  
try
{
 object objAirlineLateBound;
 Type objTypeAirline;
 object[] arrayInputParams= { "Air Scooby IC 5678" };

 //Get the type information from the progid
 objTypeAirline = Type.GetTypeFromProgID("AirlineInformation.AirlineInfo");

 // Create an instance of the object
 objAirlineLateBound = Activator.CreateInstance(objTypeAirline);

 // Invoke the 'GetAirlineTiming' method
 String str =  (String)objTypeAirline.InvokeMember("GetAirlineTiming", 
                                                   BindingFlags.Default 
                                                   | BindingFlags.InvokeMethod, 
                                                   null, 
                                                   objAirlineLateBound, 
                                                   arrayInputParams);

 System.Console.WriteLine("Late Bound Call - Air Scooby Arrives at : {0}",str);

 // Get the value of a property
 String strTime = (String)objTypeAirline.InvokeMember("LocalTimeAtOrlando",
                                                       BindingFlags.Default 
                                                       | BindingFlags.GetProperty, 
 null, 
 objAirlineLateBound,
 new object[]{});

 Console.WriteLine ("The Local Time at Orlando,Florida is : {0}", strTime);
}//try
catch(COMException e)
{
 System.Console.WriteLine("Oops- We encountered an Error. The Error message is : {0}. 
                          The Error code is {1}", e.Message,e.ErrorCode);
}//catch

Understanding COM Threading models & Apartments from a .NET application's perspective :

I remember that when I first started programming in COM, I had not yet stepped then into the murky waters of COM Threading models and apartments and had little knowledge of what they really were. I thought it was cool that my object was free threaded and simply assumed that it would be the best performing threading model. Little did I realize, what was happening under the covers. I never knew the performance penalties that would be incurred when an STA client thread created my MTA object. Also, since my object was not thread safe, I never knew I would be in trouble when concurrent threads accessed my object. Truly at that time, ignorance of COM threading models was bliss. Well, that bliss was only ephemeral and my server started crashing unexpectedly. It was then that I was forced to get my feet wet in the waters of COM Threading models & learn how each of those models behaved, how COM managed apartments, and what were the performance implications that arose when calling between two incompatible Apartments. As you know, before a thread can call into a COM object, it has to declare it's affiliation to an apartment by declaring whether it will enter an STA or MTA. STA client threads call CoInitialize(NULL) or CoInitializeEx(0, COINIT_APARTMENTTHREADED) to enter an STA apartment and MTA threads call CoInitializeEx(0, COINIT_MULTITHREADED) to enter an MTA. Similarly, in the .NET managed world, you have the option of allowing the calling thread in the managed space declare it's apartment affinity. By default, the calling thread in a managed application chooses to live in a MTA. It's as if the calling thread initialized itself with CoInitializeEx(0, COINIT_MULTITHREADED).But think about the overhead and the performance penalties that would be incurred if it were calling a classic STA COM component that was designed to be apartment threaded. The incompatible apartments will incur the overhead of an additional proxy/stub pair and this is certainly a performance penalty. You can override the default choice of Apartment for a managed thread in a .NET application by using the ApartmentState property of the System.Threading.Thread class.The ApartmentState property takes one of the following enumeration values: MTA, STA, Unknown. The ApartmentState.Unknown is equivalent to the default MTA behavior.You will need to specify the ApartmentState for the calling thread before you make any calls to the COM object. It's not possible to change the ApartmentState once the COM object has been created. So it makes sense to set the thread's ApartmentState as early as possible in your code.

 // Set the client thread ApartmentState to enter an STA
Thread.CurrentThread.ApartmentState = 
	ApartmentSTate.STA;

// Create our COM object through the Interop
MySTA objSTA = new MySTA();
objSTA.MyMethod()

COM's position in the .NET world :

In this article, you took a look at how you can expose Classic COM components to .NET applications executing under the purview of the Common Language Runtime (CLR). You saw how the COM interop seamlessly allows you to reuse existing COM components from managed code. Then, you skimmed through ways to invoke your COM component using both early binding and late binding along with ways to do runtime type checking. Finally, you saw how managed threads decalare their Apartment affiliations when invoking COM components. As a COM developer, you might wonder if it makes sense to continue writing COM components or make the transition directly into the .NET world by writing all your components and business logic code wrapped up as managed components using one of the languages such as C#, VB.NET or any of your favorite languages that generates CLR compliant managed code. In my opinion, if you have tons of COM code out there that you just cannot port to managed code overnight, it makes sense to leverage the interop's ability to reuse existing COM components from .NET applications. But, if you are starting with writing new business logic code from scratch, then it's best to wrap your code as managed components using one of the languages that generate CLR managed code. That way, you can do away with the performance penalties that are incurred while transitioning between managed and unmanaged boundaries. So eventually, we COM developers do not have to despair. Our beloved COM components will continue to play well with .NET applications. The tools provided with the .NET framework and the COM interop mechanism make it seamless from a programming perspective as to whether your .NET application is accessing a Classic COM component or a managed component. So in essence, the marriage between COM & the brave new .NET world should be a happy one and the COM that we all know and love so much will still continue to be a quintessential part of our lives.

Downloads

Download demo project - 25 Kb


Comments

  • Ghd rettetang er best for menneskene

    Posted by pletchernjv on 06/14/2013 04:44am

    [url=http://bestghdrettetangtilbud.devhub.com/] Kjøp ghd rettetang[/url] Each rosa ghd vil ha en unik sporingskode som kan verifiseres ved å følge instruksjonene i box.any folk ikke forstår at måten du kutter, stil og ha håret kan ha en dramatisk forskjell på helhetsinntrykket. Noen gang lurt på hvorfor Hollywood skuespillerinner presser 40 år gammel, men likevel hevder at nydelig ungdommelig utseende. En stor del av hemmeligheten er håret og sminke, ja jeg vet at noen velger kirurgisk tilnærming, men jeg har ikke tenkt å snakke om at nivået av forskjønnelse. [url=http://www.rettetangnorgenews.net/]ghd rettetang pris[/url] Disse nyeste GHD strykejern kommer med en beskyttende vakt slik at du kan pop det når du er ferdig å beskytte dine rettetang. Glam svart vattert roll bag er det perfekte tilbehør til Red Metallic GHD. Samt å være helt varmebestandig på innsiden roll bag gir deg også en varme bevis matte for å tillate en trygg hvilested for køllene mens i bruk.Prøver å plukke den beste flatt jern eller glattejernet kan være skremmende. Hvordan vite hvilke som ikke vil bryte, ikke vil skade håret og vil arbeide raskest? Jeg har gjort jobben for deg, å forske på de beste flatt jern og hår straighteners på markedet. Du vil finne kostbar flatt jern argumenter kjærlighet og de mer rimelige flate strykejern som ikke vil bryte på deg. [url=http://www.rettetangnorgenews.net/]rettetang ghd[/url] Med GHD IV mini styler kan man lage krøller, vipper og bølger så vel som slett hår. Det ideelle styling redskap for deg med kort hår, pannelugg eller også for menn.Med et mer elegant design og gyllne plater, som er glatte og uformet, så de lett glir igjennom håret og gir et ultra skinnende resultat. Med det forbedrede ytre skall, er stylerne kjøligere å berøre og lettere å håndtere.

    Reply
  • Why the clay’s most operative actions movement athletes best eats headphone

    Posted by motherdhmm on 06/03/2013 07:53pm

    [url=http://blog.cheapbeatsbydre.co.nz/beats-by-dre-headphones]beats by dre headphones[/url] With harmonious capabilities snowball, the headphones are increasingly high quality requirements. Which called looking for clear tough excellence, and ingenious guts a everything required. Magic profitable of the headphones is a deeply lionized brand. All the requirements of the music, little short of no one does not know. dr dre beats, also known as Hellishness, cable (Troll Hawser) is the the public¡¯s initially audio-visual tack to buckle the development of high-energy brand. Headphone is not a devilry gazabo¡¯s communicate can be turned into a ball¡¯s say, the elderly man into a sprog¡¯s spokesperson quality headphones, but the pronounce of a diabolism band produced beside the United States of headphones. We see it with dissimilar headphones, Dreadfulness¡¯s products are effectively for immature forge, design is deeply unique. Often put in an appearance in the hip-hop artists and music idols of publicity. So, in behalf of such a high-quality stereo headset ensorcelling to how we should care? [url=http://www.headphonescheaponlineaustralia.com/dre-beats-studio-c-66_68_71.html]beats studio[/url] Beats before dre purchase Headphonessets peculiar plainly various colored in joining to gre is individual as well as Vikings every learn this specific. Our take for granted is a plainly explanation this is generally speaking a tale occurs because of the Vikings dont would like to emcee through themselves for the sake a group which day in and day out in plain words permits somebody.Our own assess results picket that, nonetheless, Mutation Beats which may be certainly not certainly appropriate completely Monster headset. You can access to a pretty music with a dre in consideration headphone. Shoddy Troll Beats Headphones Online Sale. We can furnish you the ideal beats about dre headphone. [url=http://www.headphonescheaponlineaustralia.com/]beats by dre Australia[/url] Some people whisper that to travesty Jordan, Kobe Bryant, this is not fake, but if the theory of the flower of their own accessories, and Bryant suffer with their own inimitable hallucination, which has a considerable relationship and he was Europe¡¯s approach capital Milan grew up, from slight monasteriesBryant has been wearing all kinds of mould sunglasses, and sometimes there is a stingy diamond stud earrings, there is a distinguished ¡°Horribleness¡± regard cups in their head ¨C ordered the Dragon headphones. he will also take peculiar colors, garden-variety ghastly, there are violet and gold with the Lakers.

    Reply
  • You crave some tomato basil and mozzarella. Into indoor turn to account, these slippers are as phosphorescence and manueverable as sneakers.

    Posted by Soaceddew on 04/24/2013 07:15am

    Has only released respective mod color Free Inneva Woven shoes, Nike recently with another pathway to lure shoes with distinguishable styling to all [url=http://fossilsdirect.co.uk/glossarey.cfm]nike huarache[/url] eyes. This brings special printing Free Inneva Woven is a White Name of works in the series, represents shoes Italian made the assurance. Latest Safe from Inneva Woven black and melancholy are readily obtainable in two color schemes, to hand-knit Woven vamp in addition to infiltrated Italy's [url=http://fossilsdirect.co.uk/glossarey.cfm]nike huarache[/url] finest crafts, for the moment gives athletes terminate to the foot of ease, the most superior affair is the intent of Unstinting 5 configuration, barefoot know it desire allure cannot be ignored. Nike Disburden Inneva Woven SP Pale-complexioned Identify Wedge on Walk 16 at outlets about the [url=http://markwarren.org.uk/property-waet.cfm]nike air max 90[/url] trade-mark on the shelves, and on trade in narrow bearing, interested friends should pay close attention to Nike announced the news.

    Reply
  • How can I get the value of [out/ref] parameter?

    Posted by Legacy on 07/23/2003 12:00am

    Originally posted by: stanley choi

    At the moment, I do not have any problem in using COM with .NET client at all.
    However I can't get the value of [out/ref] parameter?

    Suppose I have 'A' component with 'Get'
    'Get' would be like this
    -----------------------------------------------------------
    VARIANT Get([in]String cUserId, [out/ref]VARIANT * oResult)
    -----------------------------------------------------------

    At the moment, I have no problem to getting the return and passing [in] parameter. but I can't get the second(out) parameter.

    Thanks in advance.


    Reply
  • STA apartment model

    Posted by Legacy on 04/27/2001 12:00am

    Originally posted by: Amir Mousavi

    It was wonderfull to read aboy COM and .NET. Here is what I still have problem with STA apartment threading. I have 10 clients calling same STA object. The object is a VB developed COM server running within MTS environment. How many copy of STA threads are in the same MTX process? Is there a way for me to look what thread is been used by what STA apartment?

    Reply
  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

  • The first phase of API management was about realizing the business value of APIs. This next wave of API management enables the hyper-connected enterprise to drive and scale their businesses as API models become more complex and sophisticated. Today, real world product launches begin with an API program and strategy in mind. This API-first approach to development will only continue to increase, driven by an increasingly interconnected web of devices, organizations, and people. To support this rapid growth, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds