Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Unmanaged code can wrap managed methods

0.00/5 (No votes)
28 Aug 2004 4  
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.

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).

//  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 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!!

    .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

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

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

<flags> int32/int64 processor dependent

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

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

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

.export [<ordinal>] as <export_name>

<ordinal> is an integer constant

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

The results entry for our sample is the following:

.vtfixup [1] int32 from unmanaged at VT_01

.data VT_01 = int32(0)
    .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.

//  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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here