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






4.58/5 (15投票s)
为 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。
希望对某些人有用!