The Basics of .NET Framework Interoperability

  • Dave
  • Updated date Jan 06, 2021

Platform Interoperability

 
This article gives an overview of the platform invoke service in order to show how managed code calls a native function contained in a DLL. Assume your application might need to take control of Microsoft Office Excel and perform a processing routine. Because the Excel library is not written in .NET, you would need to access it through the Component Object Model (COM). COM is a set of specifications meant to form a programming discipline for writing reusable software components that can be inserted into an operating system as a binary entity. The topic of COM and .NET interop is out of the scope of this paper. But the only way to share libraries is through interop. The platform service is an internal call mechanism that enables interoperation because of the System.Runtime.InteropSevices namespace contains classes where some of which define the methods that enable a C# program to call raw C DLL.
 
The Platform Invoke Service   Platform Invoke is a service that enables managed code to call an unmanaged function contained in raw C DLLs, functions that are implemented in dynamically-linked libraries, such as those found in the win32 API. It locates and invokes an exported function and marshals its arguments (integers, strings, arrays, structures, and so on) across the interoperation boundary as needed. The CLR used together with certain base classes of defined in the System.Runtime.InteropServices namespace the .NET Framework allows managed code to directly call functions in native code. These base classes are defined in the System.Runtime.InteropServices namespace.
 
To call a function from a DLL export from a C# program you must declare this function within a C#:
  • Declare the method by using static and extern keywords.
  • Attach the DLL import attribute to the method
  • The DLLImport attribute allows you to specify the name of the method contained in the DLL.
This example shows you how to use the DllImport attribute to create a message box by calling  MessageBoxA from user32.dll. To examine the functions contained in system component, use the dumpbin.exe tool that ships with the Microsoft Windows SDK:dumbin.exe user32.dll /all  > user.txt
 
Example
  1. /***** File: example.cs *****/  
  2. using System;  
  3. using System.Runtime.InteropServices;   
  4. // import the namespaceclass PlatformInvokeTest  
  5. // declare the class {[DllImport("user32.dll")]  
  6. //the Dllmport attribute  public static extern int MessageBoxA(  
  7. // methods    int h, string m, string c, int type);public static int Main()  
  8. // entry point {return MessageBoxA(0, "This is a Windows object!", "My MessageBox", 0);  
  9. // end of method}  
  10. // end of class}  
  11.    
  12. /****************************************************************/ 
To compile:
 
c:\%windir%\Microsoft.NET\Framework\v2.0.50727>csc.exe example.cs
 
A Closer Look at Platform Invoke
 
There are two ways that C# code can directly call unmanaged code:
  • Directly call a function exported from a DLL.
  • Call an interface method on a COM object.
For both techniques, you must provide the C# compiler with a declaration of the unmanaged function, and you may also need to provide the C# compiler with a description of how to marshal the parameters and return value to and from the unmanaged code.
 
This section tutorial consists of the following topics:
  • Calling a DLL Export Directly from C#.
  • Default Marshaling and Specifying Custom Marshaling for Parameters to Unmanaged Methods
The section includes the following examples:
  • Example 1 Using DllImport 
  • Example 2 Overriding Default Marshaling
To declare a method as having an implementation from a DLL export, do the following:
  • Declare the method with the static and extern C# keywords.
  • Attach the DllImport attribute to the method. The DllImport attribute allows you to specify the name of the DLL that contains the method. The common practice is to name the C# method the same as the exported method, but you can also use a different name for the C# method. DLLs contain export tables of semantically related functions.
  • Optionally, specify custom marshaling information for the method's parameters and return value, which will override the .NET Framework default marshaling.
Example 1
 
This example shows you how to use the DllImport attribute to output a message by calling puts frommsvcrt.dll.
  1. // PInvokeTest.cs  
  2. using System;  
  3. using System.Runtime.InteropServices;  
  4. class PlatformInvokeTest  
  5. {  
  6.     [DllImport("msvcrt.dll")]  
  7.     public static extern int puts(string c);  
  8.     [DllImport("msvcrt.dll")]  
  9.     internal static extern int _flushall(); public static void Main() { puts("Test"); _flushall(); }  

OutputTest
 
How it works:
 
The preceding example shows the minimum requirements for declaring a C# method that is implemented in an unmanaged DLL. The method PlatformInvokeTest.puts is declared with the static and extern modifiers and has the DllImport attribute which tells the compiler that the implementation comes from msvcrt.dll, using the default name of puts. To use a different name for the C# method such as putstring, you must use the EntryPoint option in the DllImport attribute, that is: [DllImport("msvcrt.dll", EntryPoint="puts")].  
 
Default Marshaling and Specifying Custom Marshaling for Parameters toUnmanaged Methods When calling an unmanaged function from C# code, the common language runtime must marshal the parameters and return values. For every .NET Framework type, there is a default unmanaged type, which the common language runtime will use to marshal data across a managed to an unmanaged function call. For example, the default marshaling for C# string values is to the type LPTSTR (pointer to TCHAR char buffer). You can override the default marshaling using the MarshalAs attribute in the C# declaration of the unmanaged function.
 
Example 2
 
This example uses the DllImport attribute to output a string. It also shows you how to override the default marshaling of the function parameters by using the MarshalAs attribute.
  1. // Marshal.cs  
  2. using System;  
  3. using System.Runtime.InteropServices;  
  4. class PlatformInvokeTest  
  5. {  
  6.     [DllImport("msvcrt.dll")]  
  7.     public static extern int puts([MarshalAs(UnmanagedType.LPStr)]     string m);  
  8.     [DllImport("msvcrt.dll")]  
  9.     internal static extern int _flushall();  
  10.     public static void Main()  
  11.     {  
  12.         puts("Hello World!"); _flushall();  
  13.     }  
  14. }
Output
 
When you run this example, the string,
 
Hello World!
 
will display on the console output.
 
To illustrate the .NET assembly a unit of deployment:
 
C:\Widows\Microsoft.NET\Framework\v2.0.50727>ildasm.exe /metadata=validate /out:Marshal.il Marshal.exe
C:\Widows\MicrosoftNET\Framework\v2.05072>ilasm.exe Marshal.il
 
Microsoft (R) .NET Framework IL Assembler.  Version 2.0.50727.1433
Copyright (c) Microsoft Corporation.  All rights reserved.
Assembling 'Marshal.il'  to EXE --> 'Marshal.exe'
The source file is ANSIAssembled method PlatformInvokeTest::Main
Assembled method PlatformInvokeTest::.ctor
Creating PE file
Emitting classes:
Class 1: PlatformInvokeTestEmitting fields and methods:
Global
Class 1 Methods: 4;
Resolving local member refs: 2 -> 2 defs, 0 refs, 0 unresolved
Emitting events and properties:
Global
Class 1
Resolving local member refs: 0 -> 0 defs, 0 refs, 0 unresolved
Writing PE file
Operation completed successfully
 
C:\Windows\Microsoft.NET\Framework\v2.0.50727>ngen Marshal.exe
Microsoft (R) CLR Native Image Generator - Version 2.0.50727.1433
Installing assembly C:\Windows\Microsoft.NET\Framework\v2.0.50727\Marshal.exe
    Compiling assembly C:\Windows\Microsoft.NET\Framework\v2.0.50727\Marshal.exe 
...
marshal, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
C:\Windows\Microsoft.NET\Framework\v2.0.50727>sn -k Marshal.key
Microsoft (R) .NET Framework Strong Name Utility  Version 2.0.50727.42
Key pair wrote to Marshal.key
 
C:\Windows\Microsoft.NET\Framework\v2.0.50727>sn -p Marshal.key Marshal.PublicKey
Microsoft (R) .NET Framework Strong Name Utility  Version 2.0.50727.42
Public key is written to Marshal.PublicKey
 
C:\Windows\Microsoft.NET\Framework\v2.0.50727>sn -tp Marshal.PublicKey
Microsoft (R) .NET Framework Strong Name Utility  Version 2.0.50727.42
Public key is
0024000004800000940000000602000000240000525341310004000001000100d7eff35e758fb
76027164c638288029538cc3c15588efb97c090dc6b45ecbf2dd3afd9ad5a13e26aa3698b9cdf87
905039c73174a4cd99a93f98e4e49a5dc4eaa62423f542056b058dd8ae69ac54b324ba76b5be29
487e8b3ad49f2be7896aa56478bfda857526ea69823dbceb019bd378a4ee607f060b7583fa9a3a
fdcfe3ae
Public key token is bbd777f8c6ef0f35
 
C:\Windows\Microsoft.NET\Framework\v2.0.50727>csc.exe /keyfile:Marshal.key Marshal.cs C:\Windows\Microsoft.NET\Framework\v2.0.50727>gacutil -i Marshal.exe
Microsoft (R) .NET Global Assembly Cache Utility.  Version 2.0.50727.42
Assembly successfully added to the cache
 
C:\Windows\Microsoft.NET\Framework\v2.0.50727>csc /doc:Marshal.xml Marshal.cs
C:\Windows\Microsoft.NET\Framework\v2.0.50727>notepad Marshal.xml
  1. <?xml version="1.0"?>  
  2. <doc>  
  3.     <assembly>  
  4.         <name>marshal</name>  
  5.     </assembly>  
  6.     <members>  
  7.     </members>  
  8.    
  9. </doc> 
C:\Windows\Microsoft.NET\Framework\v2.0.50727>xsd.exe /c Marshal.xml
Microsoft (R) Xml Schemas/DataTypes support utility
[Microsoft (R) .NET Framework, Version 2.0.50727.42]
 
Writing file 'C:\Windows\Microsoft.NET\Framework\v2.0.50727\Marshal.xsd'.
 
PInvoke provides a way for managed coe to all unmanaged functions implemented in a C DLL. The .NET developer can avoid locating and invoking the correct function export. Current technical documentation states that many companies keep large codebases of legacy code. The need to become proficient with interop is evident.