Click here to Skip to main content
15,867,934 members
Articles / Programming Languages / MSIL
Article

Unmanaged code can wrap managed methods

Rate me:
Please Sign up or sign in to vote.
4.58/5 (20 votes)
28 Aug 2004Ms-RL3 min read 102.7K   40   18
Exporting methods for the .NET class inside VB6 or unmanaged C++.

Introduction

Hi everyone! This sample is the starting point to export methods inside unmanaged language programs, such as VB, C++ or Lotus Notes, etc. Yes, I wondered looking how I can do this, but in the network, I found only samples about the usage of .NET runtime with blocks of unmanaged code such as a DLL or a COM object.

What you must know to go on

  • The base knowledge of .NET.
  • The knowledge of the manifest and some practice with the IL.
  • Must have at least disassembled a .NET program :-D.
  • Knowledge of pointers.

What you must have

  • Visual Studio 2003 ;-) (facultative).
  • .NET Framework SDK.

Background

I wondered searching and searching, searching and seeking again, and again and again, the Google oracle did not help a lot. Then, confused and terribly demoralized, I started to study, I saw the light in a wonderful book "Inside Microsoft .NET IL assembler".

My developing career started in the university with the knowledge of the cool 80x86 assembly, and today I will write some little comparison between the managed and native processor code.

Let's start with the classes

OK here we go, the way to understand something is make the thing in the simplest way.

C#
using System;
namespace ILtest
{
  public class Car
  {
    public Car()
    {
    }
    public string Drive(ref int speed)
    {
      return "my current speed is: " + speed.ToString();
    }
  }
}

That's all we want to export the method Drive in the unmanaged code, but this method must remain safe and we don't want to use the unsafe directive.

We'd like the caller to use the classic methods: LoadLibrary(...) and GetProcAddress(..., "Drive");.

I will observe how this is generated, and to do this, I use two .NET utilities from the command line: ILDASM and ILASM. The first is a decompiler into the native IL language, the second, instead is the compiler. To obtain the scope of this article we need to:

  • decompile the ILtest generated DLL,
  • make some changes (little little),
  • and compile cak with ILASM.

Come on, decompile me!

Compile without debugging information and open the Visual Studio .NET Command Prompt.

Go in the bin/release folder and observe the ILtest.dll.

Decompile with the following command:

ildasm Iltest.dll /OUT:iltest.il

Delete the res file, it is out of this scope, and examine the .IL file.

A small look at the output file

Open the iltest.il with Notepad (I prefer UltraEdit).

MSIL
//  Microsoft (R) .NET Framework IL Disassembler.  Version 1.1.4322.573
//  Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
MSIL
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
  .ver 1:0:5000:0
}
.assembly myIltest
{
  .custom instance void [mscorlib]
    System.Reflection.AssemblyKeyNameAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyKeyFileAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyDelaySignAttribute::.ctor(bool) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyConfigurationAttribute::.ctor(string) 
                                               = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyDescriptionAttribute::.ctor(string) 
                                             = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .hash algorithm 0x00008004
  .ver 1:0:1702:23004
}
.module myIltest.dll
// MVID: {2A756B7C-0BDF-4148-A2DA-4403AEF77889}
.imagebase 0x11000000
.subsystem 0x00000003
.file alignment 4096
.corflags 0x00000001
// Image base: 0x06c70000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.namespace ILtest
{
  .class public auto ansi beforefieldinit Car
         extends [mscorlib]System.Object
  {
  } // end of class Car

 

} // end of namespace ILtest


// =============================================================

 


// =============== GLOBAL FIELDS AND METHODS ===================




// =============================================================




// =============== CLASS MEMBERS DECLARATION ===================
//   note that class flags, 'extends' and 'implements' clauses
//          are provided here for information only



.namespace ILtest
{
  .class public auto ansi beforefieldinit Car
         extends [mscorlib]System.Object
  {
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  1
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  ret
    } // end of method Car::.ctor



    .method public hidebysig instance string
            Drive(int32& speed) cil managed
    {
      // Code size       17 (0x11)
      .maxstack  2
      IL_0000:  ldstr      "my current speed is: "
      IL_0005:  ldarg.1
      IL_0006:  call       instance string [mscorlib]System.Int32::ToString()
      IL_000b:  call       string [mscorlib]System.String::Concat(string,
                                                                  string)
      IL_0010:  ret
    } // end of method Car::Drive


  } // end of class Car


// =============================================================


} // end of namespace ILtest

//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file iltest.res

The IL language is very similar to a normal assembly, it reminds me of the Z80 coda, do you remember the zx spectrum? HAHAHAH! Yes, there are the load instructions!!

MSIL
.method public hidebysig instance string
        Drive(int32& speed) cil managed
{
  // Code size       17 (0x11)
  .maxstack  2
  IL_0000:  ldstr      "my current speed is: "
  IL_0005:  ldarg.1
  IL_0006:  call       instance string [mscorlib]System.Int32::ToString()
  IL_000b:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_0010:  ret
}

OK, let's start with the deep examination, and don't worry about the jockes. The first thing we can observe is, the method we want to publish is defined in the following line of code. Our method does the following instructions.

Loads the const string "my current speed is: ", loads the argument speed, and does the internal Concat method (defined statically) of the String class.

We can observe, there is no pointer reference to this function and we must create one with some few declarations:

In the beginning, we must build a v-table in order to show methods externally; this includes vtablefxup table and some export tables such as the export address table and the name pointer table, the ordinal table, the export name table, and the export directory table.

IL syntax

MSIL
.vtfixup [<num_slots>] <flags> at <data_label>

<num_slots> is an integer constant indicating the number of v-table slots grouped into one entry.

MSIL
<flags> int32/int64 processor dependent

fromunmanaged is the keyword that instructs the call is done outside the .NET framework. Here's a sample:

MSIL
.vtfixup [1] int32 fromunmanaged at VT_01
MSIL
.data VT_01 = int32(0x0100001A)

The IL Assember method for actually declaring a method as exported is:

MSIL
.export [<ordinal>] as <export_name>

<ordinal> is an integer constant

MSIL
<export_name> is the name alias of the exported method.

The results entry for our sample is the following:

MSIL
.vtfixup [1] int32 from unmanaged at VT_01

.data VT_01 = int32(0)
MSIL
    .method public hidebysig instance string 
            Drive(int32& speed) cil managed
    {
      // Code size       17 (0x11)

.vtentry 1 : 1
.export [1] as CarDrive


      .maxstack  2
      IL_0000:  ldstr      "my current speed is: "
      IL_0005:  ldarg.1
      IL_0006:  call       instance string [mscorlib]System.Int32::ToString()
      IL_000b:  call       string [mscorlib]System.String::Concat(string,

This is all we need besides a little fix: .corflags 0x00000001 changed to .corflags 0x00000002.

Now the code is ready, and I publish the new one.

MSIL
//  Microsoft (R) .NET Framework IL Disassembler.  Version 1.1.4322.573
//  Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
  .ver 1:0:5000:0
}
.assembly myIltest
{
  .custom instance void [mscorlib]
    System.Reflection.AssemblyKeyNameAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyKeyFileAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyDelaySignAttribute::.ctor(bool) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyConfigurationAttribute::.ctor(string) 
                                               = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyDescriptionAttribute::.ctor(string) 
                                             = ( 01 00 00 00 00 ) 
  .custom instance void [mscorlib]
    System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
  .hash algorithm 0x00008004
  .ver 1:0:1702:23004
}
.module myIltest.dll
// MVID: {2A756B7C-0BDF-4148-A2DA-4403AEF77889}
.imagebase 0x11000000
.subsystem 0x00000003
.file alignment 4096
.corflags 0x00000002

.vtfixup [1] int32 from unmanaged at VT_01
.data VT_01 = int32(0)


// Image base: 0x06c70000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.namespace ILtest
{
  .class public auto ansi beforefieldinit Car
         extends [mscorlib]System.Object
  {
  } // end of class Car

} // end of namespace ILtest


// =============================================================


// =============== GLOBAL FIELDS AND METHODS ===================


// =============================================================


// =============== CLASS MEMBERS DECLARATION ===================
//   note that class flags, 'extends' and 'implements' clauses
//          are provided here for information only

.namespace ILtest
{
  .class public auto ansi beforefieldinit Car
         extends [mscorlib]System.Object
  {
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor() cil managed
    {
.vtentry 1 : 1
.export [1] as CarDrive

      // Code size       7 (0x7)
      .maxstack  1
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  ret
    } // end of method Car::.ctor

    .method public hidebysig instance string 
            Drive(int32& speed) cil managed
    {
      // Code size       17 (0x11)
      .maxstack  2
      IL_0000:  ldstr      "my current speed is: "
      IL_0005:  ldarg.1
      IL_0006:  call       instance string [mscorlib]System.Int32::ToString()
      IL_000b:  call       string [mscorlib]System.String::Concat(string,
                                                                  string)
      IL_0010:  ret
    } // end of method Car::Drive

  } // end of class Car


// =============================================================

} // end of namespace ILtest

//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file iltest.res

In the practice, we had wrote an external entry point to our function

Last step

We can compile back the DLL with this command:

ILasm ILtest.il /out:iltest.dll

That's all! Other unmanaged languages can see the Drive method! Isn't it great?!

Point of interest

The parameters must be passed by reference, i.e., Drive(ref string speed) -> Drive(int32& speed).

References

  • "Inside Microsoft .NET Assembler" - Microsoft Press.

I hope it is useful for somebody!

License

This article, along with any associated source code and files, is licensed under Microsoft Reciprocal License


Written By
Architect
Italy Italy
Hi.i.prefer not to tell so much about my biografy, i am an eternal student. Big Grin | :-D

Comments and Discussions

 
Questioncalling the function causes error Pin
RaMMicHaeL5-Jun-13 5:01
RaMMicHaeL5-Jun-13 5:01 
GeneralMy vote of 5 Pin
dhsc4-Dec-12 2:57
dhsc4-Dec-12 2:57 
GeneralMy vote of 1 Pin
misaqyrn967715-Oct-12 1:59
misaqyrn967715-Oct-12 1:59 
GeneralRe: My vote of 1 Pin
mla15422-Jan-13 3:49
mla15422-Jan-13 3:49 
GeneralRe: My vote of 1 Pin
DelphiCoder25-May-14 17:41
DelphiCoder25-May-14 17:41 
GeneralRe: My vote of 1 Pin
Emilio Reale6-Mar-23 4:33
Emilio Reale6-Mar-23 4:33 
GeneralMy vote of 5 Pin
ggxPrince24-Feb-12 16:14
ggxPrince24-Feb-12 16:14 
GeneralMy vote of 4 Pin
danah gaz10-Jul-11 5:30
professionaldanah gaz10-Jul-11 5:30 
Thanks for the article, man I almost gave you a one (i was looking at another thread and mixed them up).

Good work, but your article needs an edit.
from unmanaged
should be
fromunmanaged

thanks!
GeneralBetter solution!! Pin
Wojciech Nagórski27-Aug-10 22:14
Wojciech Nagórski27-Aug-10 22:14 
GeneralRe: Better solution!! Pin
Amegon11-Apr-11 7:12
Amegon11-Apr-11 7:12 
GeneralRe: Better solution!! Pin
Verstoerer15-Aug-11 21:50
Verstoerer15-Aug-11 21:50 
GeneralError from ILasm...token "from" unknown Pin
SBGTrading24-Mar-10 11:56
SBGTrading24-Mar-10 11:56 
General.NET 2.0 Pin
orlum16-Sep-09 19:50
orlum16-Sep-09 19:50 
Question.export, .vtentry? Pin
Đonny12-Mar-09 14:27
Đonny12-Mar-09 14:27 
GeneralDisposal Pin
Wraith229-Aug-04 8:43
Wraith229-Aug-04 8:43 
GeneralRe: Disposal Pin
Emilio Reale29-Aug-04 10:16
Emilio Reale29-Aug-04 10:16 
GeneralRe: Disposal Pin
hal663-Dec-06 9:55
hal663-Dec-06 9:55 
GeneralRe: Disposal Pin
ArchAngel1233-Aug-10 9:05
ArchAngel1233-Aug-10 9:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.