Creating interfaces with identical functionality (method and properties) in various languages

Please Sign up or sign in to vote.

Introduction

In the recent releases of Microsoft* Visual Studio 2012 and Windows 8* with Windows Runtime as a common foundation, there are many new things to investigate. This article demonstrates how to implement interfaces with equal set of methods and properties in various languages. This can be valuable in iterative development or just helpful in better understanding interfaces in Windows Runtime.

Background

In order to estimate syntax complexity and details of interfaces implementations with further comparison of results, a small task, solving a small problem, was taken since small things with equal functionality are easy to compare. Moreover, the results of compilation into MSIL are also included.

Using the code

A given sample idea stems from a Hybrid JavaScript and C++ online Visual Studio sample. It contains a UI JavaScript project and 3 worker projects. The worker projects are respectively implemented in C#, C++/CX and C++/WRL and the UI project is in JavaScript. Each worker project interlaces the picture with its own method and returns the calculation time through its elapsedTime property.

From JavaScript code the functionality looks as follows:

var myObject = new ImagingNamespace.ImageFilter(); myObject.interlaceImage(srcFile.path, dstImgPath); var calcTime = myObject.elapsedTime;

Interfaces

First, this is an IDL interface for C++/WRL. It looks almost as a regular COM interface with a few new details:

[ uuid (c350a862-0132-4da5-86b2-db0b47bc6913), version (COMPONENT_VERSION)] interface IImageCustomProcessing : IInspectable { HRESULT InterlaceImage([ in ] HSTRING inFile, [ in ] HSTRING outFile); [ propget ] HRESULT ElapsedTime([out, retval ] DOUBLE* pVal); }

This is the result of compilation of the interface into IL:

. class interface public auto ansi abstract flag( 4000 ) ImagingCppNative.IImageCustomProcessing { .custom instance void [Windows.Foundation]Windows.Foundation.Metadata.VersionAttribute::.ctor( uint32 ) = ( 01 00 00 00 00 01 00 00 ) .custom instance void [Windows.Foundation]Windows.Foundation.Metadata.GuidAttribute::.ctor( uint32, uint16, uint16, uint8, uint8, uint8, uint8, uint8, uint8, uint8, uint8) = ( 01 00 62 a8 50 c3 32 01 a5 4d 86 b2 db 0b 47 bc 69 13 00 00 ) .property instance float64 ElapsedTime() { .get instance float64 ImagingCppNative.IImageCustomProcessing::get_ElapsedTime() { } } .method public hidebysig newslot abstract virtual instance void InterlaceImage ( [in] string inFile, [in] string outFile ) cil managed { } }

C++/CX interface is implemented in the following way:

public interface class IImageCustomProcessing { void InterlaceImage( String ^ inFile, String ^ outFile); property double ElapsedTime { double get(); } };

The corresponding IL is below:

. class interface public auto ansi abstract flag( 4200 ) ImagingCppCX.IImageCustomProcessing { .custom instance void [Windows]Windows.Foundation.Metadata.VersionAttribute::.ctor( uint32 ) = ( 01 00 01 00 00 00 00 00 ) .custom instance void [Windows]Windows.Foundation.Metadata.GuidAttribute::.ctor(uint32, uint16, uint16, uint8, uint8, uint8, uint8, uint8, uint8, uint8, uint8) = ( 01 00 d4 3e d0 d8 70 23 11 3b be 25 82 40 3e 81 01 52 00 00 ) .property instance float64 ElapsedTime() { .get instance float64 ImagingCppCX.IImageCustomProcessing::get_ElapsedTime() { } } .method public hidebysig newslot abstract virtual instance void InterlaceImage ( string inFile, string outFile ) cil managed { } }

The C# interface looks as usual:

public interface IImageCustomProcessing { void InterlaceImage( String inFile, String outFile); double ElapsedTime { get ; } }

IL for this interface:

. class interface public auto ansi abstract flag( 4000 ) ImagingCS.IImageCustomProcessing { .custom instance void [Windows]Windows.Foundation.Metadata.VersionAttribute::.ctor( uint32 ) = ( 01 00 00 00 00 01 00 00 ) .custom instance void [Windows]Windows.Foundation.Metadata.GuidAttribute::.ctor(uint32, uint16, uint16, uint8, uint8, uint8, uint8, uint8, uint8, uint8, uint8) = ( 01 00 72 2c c0 d1 59 00 d9 50 63 5f 66 f4 3e 7f 60 91 00 00 ) .property instance float64 ElapsedTime() { .get instance float64 ImagingCS.IImageCustomProcessing::get_ElapsedTime() { } } .method public hidebysig newslot abstract virtual instance void InterlaceImage ( [in] string inFile, [in] string outFile ) cil managed { } }

It's not hard to notice that the resulting MSIL for all languages is almost completely equal, no matter which language has been used.

Classes

This section describes how to bring these interfaces to life and to make them actually work.

The IDL interface in C++/WRL is implemented in the following manner:

class ImageFilter : public RuntimeClass<::ABI::ImagingCppNative::IImageCustomProcessing> { InspectableClass(RuntimeClass_ImagingCppNative_WinRTImageProcComponent, TrustLevel::BaseTrust); ... public : IFACEMETHOD(InterlaceImage)(_In_ HSTRING inFile, _In_ HSTRING outFile); IFACEMETHOD(get_ElapsedTime)(DOUBLE *time); ... } ActivatableClass(ImageFilter);

C++/CX implementation looks like this:

public ref class ImageFilter sealed : IImageCustomProcessing { ... public : virtual void InterlaceImage( String ^ inFile, String ^ outFile); virtual property double ElapsedTime { double get() { return elapsedTime; } } ... }

The C# code is the simplest:

public sealed class ImageFilter : IImageCustomProcessing { ... public void InterlaceImage( String inFile, String outFile) { ... } public double ElapsedTime { get { return elapsedTime; } } ... }

C# implementation remark

C# in .NET 4.5 targeting Metro UI (or Microsoft design language, or with WinMD output) is made in such a way that all IO operations are asynchronous. Therefore, the special trick has been done to make loading and unloading synchronous and align the code idea with C++ pieces.

The following code snippet runs an asynchronous piece of code in a synchronous method.

int SyncMethod() { var asyncTask = new TaskFactory().StartNew( async () = > { return 0 ; }); var syncTask = asyncTask.ContinueWith(tt = > { return tt.Result.Result; }); syncTask.Wait(); int taskResult = syncTask.Result return taskResult; }

Please notice that the lambda function in StartNew constructor is asynchronous and on the contrary the lambda function in ContinueWith is regularly synchronous. In this case, taskResult will be assigned only after asyncTask and syncTask both finish.

Conclusion

As a result, interfaces with completely equal functionality may be created in different languages and notably resulting MSIL code for different interfaces looks equal, no matter which tool--IDL, CL or CSC--has been used.

History

December 15, 2012 - Initial publishing.