65.9K
CodeProject 正在变化。 阅读更多。
Home

非托管代码可以包装托管方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.58/5 (15投票s)

2004 年 8 月 29 日

Ms-RL

3分钟阅读

viewsIcon

107124

为 VB6 或非托管 C++ 中的 .NET 类导出方法。

引言

大家好!这个示例是导出方法到非托管语言程序(如 VB、C++ 或 Lotus Notes 等)的起点。是的,我曾疑惑如何做到这一点,但在网上,我只找到关于使用 .NET 运行时与非托管代码块(如 DLL 或 COM 对象)的示例。

继续前进需要了解的内容

  • .NET 的基础知识。
  • 清单的知识和一些 IL 实践。
  • 至少要反编译过一个 .NET 程序 :-D。
  • 指针知识。

您需要准备什么

  • Visual Studio 2003 ;-)(可选)。
  • .NET Framework SDK。

背景

我不断地搜索、搜索、再搜索、再寻找,一遍又一遍,Google 神谕也没有提供太多帮助。然后,困惑且士气低落,我开始学习,在一个精彩的书《Inside Microsoft .NET IL assembler》中看到了曙光。

我的开发生涯始于大学,当时我对很酷的 80x86 汇编有所了解,今天我将写一些关于托管和原生处理器代码的小比较。

让我们从类开始

好的,我们开始吧,理解事物的最好方法就是以最简单的方式去做。

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

我们只想将方法 Drive 导出到非托管代码,但此方法必须保持安全,并且我们不想使用 unsafe 指令。

我们希望调用者使用经典的 **LoadLibrary(...)** 和 **GetProcAddress(..., "Drive");** 方法。

我将观察这是如何生成的,为此,我使用两个命令行 .NET 工具:ILDASM 和 ILASM。第一个是将 IL 反编译为原生 IL 语言的工具,而第二个是编译器。为了达到本文的范围,我们需要

  • 反编译 ILtest 生成的 DLL,
  • 做一些(微小的)更改,
  • 然后使用 ILASM 编译它。

来吧,反编译我!

在不带调试信息的情况下进行编译,并打开 Visual Studio .NET 命令提示符。

进入 bin/release 文件夹并观察 ILtest.dll

使用以下命令进行反编译

ildasm Iltest.dll /OUT:iltest.il

删除 res 文件,它超出了本文范围,并检查 .IL 文件。

简要查看输出文件

用记事本(我更喜欢 UltraEdit)打开 iltest.il

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

IL 语言非常像普通的汇编,让我想起了 Z80 代码,你还记得 ZX Spectrum 吗?哈哈!是的,有加载指令!

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

好的,让我们开始深入研究,不要担心玩笑。我们可以观察到的第一件事是,我们要发布的函数定义在以下代码行中。我们的函数执行以下指令。

加载常量字符串“my current speed is: ”,加载参数 speed,并执行 String 类的内部 Concat 方法(静态定义)。

我们可以观察到,没有指向此函数的指针引用,并且我们需要通过一些声明来创建一个。

一开始,我们必须构建一个 v-table 以便在外部显示函数;这包括 vtablefxup 表和一些导出表,如导出地址表、名称指针表、序数表、导出名称表和导出目录表。

IL 语法

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

<num_slots> 是一个整数常量,表示分组到一个条目中的 v-table 插槽数。

<flags> int32/int64 processor dependent

fromunmanaged 是指示调用发生在 .NET 框架之外的关键字。下面是一个示例

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

IL Assembler 方法,用于实际将方法声明为已导出,如下所示:

.export [<ordinal>] as <export_name>

<ordinal> 是一个整数常量。

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

我们示例的结果条目如下:

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

这就是我们所需要的一切,此外还有一个小修正:将 .corflags 0x00000001 更改为 .corflags 0x00000002

现在代码准备就绪,我发布新的代码。

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

实际上,我们为我们的函数编写了一个外部入口点。

最后一步

我们可以使用此命令将 DLL 编译回去

ILasm ILtest.il /out:iltest.dll

就这些了!其他非托管语言可以看到 Drive 方法!是不是很棒?!

关注点

参数必须通过引用传递,即 Drive(ref string speed) -> Drive(int32& speed)

参考文献

  • 《Inside Microsoft .NET Assembler》- Microsoft Press。

希望对某些人有用!

© . All rights reserved.