.NET Interoperability at a Glance 3 – Unmanaged Code Interoperation

هذه المقالة متوفرة باللغة العربية أيضا، اقرأها هنا.

See more Interoperability examples here.

Contents

Contents of this article:

  • Contents
  • Read also
  • Overview
  • Unmanaged Code Interop
  • Interop with Native Libraries
  • Interop with COM Components
  • Interop with ActiveX Controls
  • Summary
  • Where to go next

Read also

More from this series:

Overview

This is the last article in this series, it talks about unmanaged code interoperation; that’s, interop between .NET code and other code from other technologies (like Windows API, native libraries, COM, ActiveX, etc.)

Be prepared!

Introduction

Managed code interop wasn’t so interesting, so it’s the time for some fun. You might want to call some Win32 API functions, or it might be interesting if you make use of old, but useful, COM components. Let’s start!

Unmanaged Code Interop

Managed code interoperation isn’t so interesting, but this is. Unmanaged interoperation is not easy as the managed interop, and it’s also much difficult and much harder to implement. In unmanaged code interoperation, the first system is the .NET code; the other system might be any other technology including Win32 API, COM, ActiveX, etc. Simply, unmanaged interop can be seen in three major forms:

  1. Interoperation with Native Libraries.
  2. Interoperation with COM components.
  3. Interoperation with ActiveX.

Interop with Native Libraries

This is the most famous form of .NET interop with unmanaged code. We usually call this technique, Platform Invocation, or simply PInvoke. Platform Invocation or PInvoke refers to the technique used to call functions of native unmanaged libraries such as the Windows API.

To PInvoke a function, you must declare it in your .NET code. That declaration is called the Managed Signature. To complete the managed signature, you need to know the following information about the function:

  1. The library file which the function resides in.
  2. Function name.
  3. Return type of the function.
  4. Input parameters.
  5. Other relevant information such as encoding.

Here comes a question, how could we handle types in unmanaged code that aren’t available in .NET (e.g. BOOL, LPCTSTR, etc.)?

The solution is in Marshaling. Marshaling is the process of converting unmanaged types into managed and vice versa (see figure 1.) That conversion can be done in many ways based on the type to be converted. For example, BOOL can simply be converted to System.Boolean, and LPCTSTR can be converted to System.String, System.Text.StringBuilder, or even System.Char[]. Compound types (like structures and unions) are usually don’t have counterparts in .NET code and thus you need to create them manually. Read our book about marshaling here.

Figure 1 - The Marshaling Process
Figure 1 – The Marshaling Process

To understand P/Invoke very well, we’ll take an example. The following code switches between mouse button functions, making the right button acts as the primary key, while making the left button acts as the secondary key.

In this code, we’ll use the SwapMouseButtons() function of the Win32 API which resides in user32.dll library and has the following declaration:

BOOL SwapMouseButton(
    BOOL fSwap
    );

Of course, the first thing is to create the managed signature (the PInvoke method) of the function in .NET:

// C#
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern bool SwapMouseButton(bool fSwap);
' VB.NET
Declare Auto Function SwapMouseButton Lib "user32.dll" _
    (ByVal fSwap As Boolean) As Boolean

Then we can call it:

// C#
public void MakeRightButtonPrimary()
{
    SwapMouseButton(true);
}
public void MakeLeftButtonPrimary()
{
    SwapMouseButton(false);
}
' VB.NET
Public Sub MakeRightButtonPrimary()
    SwapMouseButton(True)
End Sub
Public Sub MakeLeftButtonPrimary()
    SwapMouseButton(False)
End Sub

Interop with COM Components

The other form of unmanaged interoperation is the COM Interop. COM Interop is very large and much harder than P/Invoke and it has many ways to implement. For the sake of our discussion (this is just a sneak look at the technique,) we’ll take a very simple example.

COM Interop includes all COM-related technologies such as OLE, COM+, ActiveX, etc.

Of course, you can’t talk directly to unmanaged code. As you’ve seen in Platform Invocation, you have to declare your functions and types in your .NET code. How can you do this? Actually, Visual Studio helps you almost with everything so that you simply to include a COM-component in your .NET application, you go to the COM tab of the Add Reference dialog (figure 2) and select the COM component that you wish to add to your project, and you’re ready to use it!

Figure 2 - Adding Reference to SpeechLib Library
Figure 2 – Adding Reference to SpeechLib Library

When you add a COM-component to your .NET application, Visual Studio automatically declares all functions and types in that library for you. How? It creates a Proxy library (i.e. assembly) that contains the managed signatures of the unmanaged types and functions of the COM component and adds it to your .NET application.

The proxy acts as an intermediary layer between your .NET assembly and the COM-component. Therefore, your code actually calls the managed signatures in the proxy library that forwards your calls to the COM-component and returns back the results.

Keep in mind that proxy libraries also called Primary Interop Assemblies (PIAs) and Runtime Callable Wrappers (RCWs.)

Best mentioning that Visual Studio 2010 (or technically, .NET 4.0) has lots of improved features for interop. For example, now you don’t have to ship a proxy/PIA/RCW assembly along with your executable since the information in this assembly can now be embedded into your executable; this is what called, Interop Type Embedding.

Of course, you can create your managed signatures manually, however, it’s not recommended especially if you don’t have enough knowledge of the underlying technology and the marshaling of functions and types (you know what’s being said about COM!)

As an example, we’ll create a simple application that reads user inputs and speaks it. Follow these steps:

  1. Create a new Console application.
  2. Add a reference to the Microsoft Speech Object Library (see figure 2.)
  3. Write the following code and run your application:
// C#
using SpeechLib;
static void Main()
{
    Console.WriteLine("Enter the text to read:");
    string txt = Console.ReadLine();
    Speak(txt);
}
static void Speak(string text)
{
    SpVoice voice = new SpVoiceClass();
    voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault);
}
' VB.NET
Imports SpeechLib
Sub Main()
    Console.WriteLine("Enter the text to read:")
    Dim txt As String = Console.ReadLine()
    Speak(txt)
End Sub
Sub Speak(ByVal text As String)
    Dim voice As New SpVoiceClass()
    voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault)
End Sub

If you are using Visual Studio 2010 and .NET 4.0 and the application failed to run because of Interop problems, try disabling Interop Type Embedding feature from the properties on the reference SpeechLib.dll.

Interop with ActiveX Controls

ActiveX is no more than a COM component that has an interface. Therefore, nearly all what we have said about COM components in the last section can be applied here except the way we add ActiveX components to our .NET applications.

To add an ActiveX control to your .NET application, you can right-click the Toolbox, select Choose Toolbox Items, switch to the COM Components tab and select the controls that you wish to use in your application (see figure 3.)

Figure 3 - Adding WMP Control to the Toolbox
Figure 3 – Adding WMP Control to the Toolbox

Another way is to use the aximp.exe tool provided by the .NET Framework (located in Program Files\Microsoft SDKs\Windows\v7.0A\bin) to create the proxy assembly for the ActiveX component:

aximp.exe "C:\Windows\System32\wmp.dll"

Not surprisingly, you can create the proxy using the way for COM components discussed in the previous section, however, you won’t see any control that can be added to your form! That way creates control class wrappers for unmanaged ActiveX controls in that component.

Summary

So, unmanaged code interoperation comes in two forms: 1) PInvoke: interop with native libraries including the Windows API 2) COM-interop which includes all COM-related technologies like COM+, OLE, and ActiveX.

To PInvoke a method, you must declare it in your .NET code. The declaration must include 1) the library which the function resides in 2) the return type of the function 3) function arguments.

COM-interop also need function and type declaration and that’s usually done for you by the Visual Studio which creates a proxy (also called RCW and PIA) assembly that contains managed definitions of the unmanaged functions and types and adds it to your project.

Where to go next

Read more about Interoperability here.

More from this series:

Similar Posts:


Random Posts:


Recent Posts:

  • Anonymous

    I have data in bytes then i convert into structure then some time i got the error HRESULT X08000 can’t acccess shared memory or memory is currept.
    can you please help me out where is below wrong so some time intPtr gets open not release the memory.
    kindly once look over this code…

    private void run()
    {

    //m_messageQueue = MessageQueue.Create(@”.private$myquelocal”);
    // Create a service thread:
    m_serviceThread = new Thread(new ThreadStart(MSMQServiceThreadDelegate));

    // Start Service Thread:
    m_serviceThread.Start();
    }

    private void MSMQServiceThreadDelegate()
    {

    MessageQueue m_messageQueue = new MessageQueue(@”.private$myquelocal”);

    Console.WriteLine(“step1 create queue”);

    m_messageQueue.Formatter = new BinaryMessageFormatter();

    Console.WriteLine(“step2 binary”);

    IAsyncResult result = null;
    System.Messaging.Message message = null;
    WaitHandle[] waitHandleArray = new WaitHandle[2];
    waitHandleArray[0] = new ManualResetEvent(true);// ManualResetEvent(false);// m_stopServiceEvent;//Service Thread Stop Event:
    bool bReceiveMessages = true;
    while (bReceiveMessages)
    {
    try
    {
    Console.WriteLine(“step3 binary”);

    result = m_messageQueue.BeginReceive( TimeSpan.FromMinutes(1),m_messageQueue );
    waitHandleArray[1] = result.AsyncWaitHandle;

    Console.WriteLine(“step4 binary”);

    Console.WriteLine(“step5 binary”);

    byte[] buffer = (byte[])message.Body;

    ///**Start convert **//

    TBCastMessageHeader bcastHeader = new TBCastMessageHeader();
    IntPtr bcastHeaderPtr;

    bcastHeaderPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(TBCastMessageHeader)));
    // byte[] buffer = listener.Receive(ref groupEP);
    Marshal.Copy(buffer, 0, bcastHeaderPtr, buffer.Length);
    bcastHeader = (TBCastMessageHeader)(Marshal.PtrToStructure(bcastHeaderPtr, typeof(TBCastMessageHeader)));

    try
    {
    switch (bcastHeader.MessageCode)
    {

    case 1001: //usp_Insert1001data

    TMarketUpdateMsg marketupdMsg;
    IntPtr marketPtr ;
    marketPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(TMarketUpdateMsg)));
    Marshal.Copy(buffer, 0, marketPtr, buffer.Length);
    marketupdMsg = (TMarketUpdateMsg)(Marshal.PtrToStructure(marketPtr, typeof(TMarketUpdateMsg)));

    try
    {
    for (int i = 0; i < marketupdMsg.NoOfRecs; i++)
    {

    Console.WriteLine(marketupdMsg.BCastMessageHeader.TimeStamp.ToString());
    }
    }
    catch (Exception ex) { }
    finally { Marshal.FreeHGlobal(marketPtr); GC.Collect(); }

    break;

    }
    }
    catch (Exception ecp)
    { }
    finally { Marshal.FreeHGlobal(bcastHeaderPtr); GC.Collect(); }
    //end
    //}
    }
    catch (Exception e)
    {

    Console.WriteLine(e.Message);

    Console.Read();

    if (message != null)
    m_messageQueue.Send(message);

    bReceiveMessages = false;

    }
    }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 0x1)]
    public struct TBCastMessageHeader
    {
    [MarshalAs(UnmanagedType.U2)]
    public ushort MessageCode;
    [MarshalAs(UnmanagedType.U4)]
    public uint TimeStamp;
    [MarshalAs(UnmanagedType.U2)]
    public ushort MessageLength;
    [MarshalAs(UnmanagedType.I1)]
    public sbyte NumberOfDecimals;
    [MarshalAs(UnmanagedType.U2)]
    public ushort Reserved;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 0x1)]
    public struct TMarketUpdateMsg
    {
    public TBCastMessageHeader BCastMessageHeader;
    [MarshalAs(UnmanagedType.U1)]
    public byte NoOfRecs;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
    public TMarketInfo[] MarketInfo;//array [0..MARKET_PER_PACKET - 1] of TMarketInfo;
    }