纯 .NET C# DirectShow 过滤器






4.99/5 (76投票s)
本文介绍了如何在 .NET 中创建 DirectShow 过滤器,其中包含 BaseClasses 和一些示例。
- 下载示例过滤器二进制文件 - 124.1 KB
- 下载 BaseClasses 和过滤器源代码 - 143.1 KB
- 下载示例应用程序源代码 - 75.8 KB
- 下载示例应用程序二进制文件 - 134.4 KB
引言
在这篇文章中,我将介绍如何用纯 C# 创建 DirectShow 过滤器。我同样用纯 C# 制作了 BaseClasses 库和几个示例,以展示它们可以多么容易地被使用。我认为初级的多媒体开发者可以使用我的库,但要扩展它,你需要了解 COM、封送处理和线程。我建议你阅读我之前的文章,因为我不会重复其中描述的主要内容。
内容概述
过滤器的源代码包含两个项目:BaseClasses 和 ExampleFilters。BaseClasses 库包含以下类:
BaseEnum
- 枚举的基类EnumPins
-IEnumPins
接口的实现。EnumMediaTypes
-IEnumMediaTypes
接口的实现。BasePin
- 引脚的基类。BaseInputPin
- 输入引脚的基类。BaseOutputPin
- 输出引脚的基类。BaseFilter
- 过滤器实现的基类。TransformInputPin
- 变换过滤器的输入引脚类。TransformOutputPin
- 变换过滤器的输出引脚类。TransformFilter
- 变换过滤器的基类。TransInPlaceInputPin
- 原地变换过滤器的输入引脚类。TransInPlaceOutputPin
- 原地变换过滤器的输出引脚类。TransInPlaceFilter
- 原地变换过滤器的基类。RenderedInputPin
- 渲染过滤器的输入引脚基类。AMThread
- 线程实现的基类。ManagedThread
-AMThread
的托管线程实现。SourceStream
- 基输出流引脚类。BaseSourceFilter
- 源过滤器实现的基类。OutputQueue
- 实现用于传递媒体样本的队列的类。BasePropertyPage
- 用于实现过滤器属性页的类。PosPassThru
- 处理查找命令,将它们传递给下一个过滤器的类。RendererPosPassThru
- 处理渲染过滤器查找命令的类。RendererInputPin
- 为BaseRendererFilter
类实现输入引脚的类。MessageDispatcher
- 处理用户线程未阻塞通知并分发窗口消息的类。AsyncStream
-IAsyncReader
接口的System.IO.Stream
实现。COMStream
-IStream
接口的System.IO.Stream
实现。PacketData
- 用于描述队列媒体数据的基类。PacketsQueue
-PacketData
对象的队列类。BitStreamReader
- 用于从System.IO.Stream
对象读取二进制数据的辅助类。它支持IStream
和IAsyncReader
接口。允许按位、按数组或按结构读取数据。类还支持 golomb SE 和 UE 值。读取通过缓存进行。DemuxTrack
- 实现分离器轨道的基类。FileParser
- 实现分离器文件解析支持的基类。SplitterInputPin
- 实现BaseSplitterFilter
输入引脚的类。SplitterOutputPin
- 为BaseSplitterFilter
和BaseFileSourceFilter
对象实现输出引脚。BaseSplitter
- 实现分离器或文件源实现的类核心功能的类。BaseSplitterFilter
- 实现具有单个输入引脚的分离器过滤器的基类,该引脚支持连接到IAsyncReader
,输出引脚在连接后创建。过滤器支持查找。BaseFileSourceFilter
- 实现文件源过滤器的基类,它没有输入引脚,但支持IFileSourceFilter
接口,输出引脚在文件加载后创建。
解决方案中的另一个项目包含以下示例过滤器:
NullInPlaceFilter
- 一个原地变换过滤器,可用作基类。NullTransformFilter
- 一个变换过滤器,可用作基类。DumpFilter
- 用于将传入数据保存到文件的过滤器。TextOverFilter
- 一个变换过滤器,在传入视频上显示叠加文本。ImageSourceFilter
- 推送源过滤器,将加载的图像文件显示为视频流。ScreenCaptureFilter
- 推送源过滤器,捕获显示并将其作为视频流提供。VideoRotationFilter
- 一个变换过滤器,将视频旋转 90 度。WavDestFilter
- 用于将传入的 PCM 数据保存到 WAV 文件的过滤器。AudioChannelFilter
- 一个变换过滤器,用于合并传入的 PCM 音频数据并将其发送到指定的通道。InfTeeFilter
- 将传入的样本传递给任何已连接输出引脚的过滤器。NullRendererFilter
- 实现为标准的“Null Renderer”过滤器的过滤器,它继承自基渲染过滤器并展示了需要覆盖的基本方法。WAVESplitterFilter
- 实现 WAVE 文件格式的分离器过滤器的过滤器。WAVESourceFilter
- 实现 WAVE 文件格式的源过滤器的过滤器。NetworkSyncFilter
和NetworkSource
过滤器是实现组播视频流的示例。
此外,我还包含了带有示例应用程序的解决方案,向您展示了如何使用我的辅助类,并将过滤器嵌入到应用程序中使用,而无需注册。
DxCapture
- 示例展示了如何构建视频捕获应用程序。DxGrabber
- 示例用法样本抓取器。DxPlayer
- 基本的音频视频播放器应用程序。WavCapture
- 示例演示了如何构建音频捕获应用程序,该示例嵌入了 wave out 过滤器。WavExtract
- 示例展示了如何从媒体文件中提取音频数据并将其保存为 WAVE 格式,该示例嵌入了 wave out 过滤器。WavPlay
- 示例用法嵌入的 WAVE 源过滤器来播放 .wav 音频文件。
BaseClasses
BaseClasses 还使用了我的类库和 COM 辅助对象中的一些内容。这些对象在我的先前文章中有部分描述。对于跟踪和调试,我建议使用 TRACE
、TRACE_ENTER
和 ASSERT
函数;**不要**使用 Debug.Write
和抛出异常——具体原因在之前的文章中有描述。此外,我建议阅读我之前关于线程的描述(因为我不想在这里重复)。我的实现方法与 Microsoft 的原生方法类似,所以我将只描述区别。好的,如果你关注了所有这些方面,我们就开始回顾吧。
过滤器注册
过滤器注册过程与 .NET COM 对象类似。过滤器 DLL **应该**签名。你可以将类型库作为非托管资源嵌入到 DLL 中,但这并非强制。在示例过滤器中,注册是自动执行的,请参阅 *install.bat* 和 *uninstall.bat* 文件以及生成后事件。你可以在项目设置中指定注册,但这可能不起作用。我让过滤器注册非常简单。为此,有一个类属性,带有一个不同的构造函数:
// Class declaration
[AttributeUsage(AttributeTargets.Class)]
public class AMovieSetup : Attribute
//.............
// Constructors
public AMovieSetup()
public AMovieSetup(bool _register)
public AMovieSetup(string _name)
public AMovieSetup(Merit _merit)
public AMovieSetup(Merit _merit, Guid _category)
public AMovieSetup(string _name, Merit _merit)
public AMovieSetup(string _name, Merit _merit, Guid _category)
该属性应指定为要注册为 DirectShow 过滤器的类。类应继承自任何基过滤器类,例如 BaseFilter
、TransInPlaceFilter
、TransformFilter
或 BaseSourceFilter
。在属性中,你可以指定过滤器的 name
、merit
和 category
。如果未指定名称,则将使用过滤器类中指定的名称。默认 Merit
值为 *DoNotUse*,默认注册 category
为 *AmLegacyFiltersCategory*。过滤器的另一个**必需**属性是 **Guid**,它将是过滤器的 CLSID。过滤器 BaseFilter
类中的以下例程展示了如何执行注册和注销:
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type _type)
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type _type)
过滤器声明示例
// Here name not specified and will be used name setted in constructor
// Category will be used by default AmLegacyFilters an merit - do not use
[ComVisible(true)]
[Guid("eeb3eef7-0592-491b-b7d4-8c65763c79c6")] // Filter CLSID
[AMovieSetup(true)] // We should register
public class NullInPlaceFilter : TransInPlaceFilter
{
public NullInPlaceFilter()
: base("CSharp Null InPlace Filter") {} // this name will be used
//..............
}
与原生 BaseClasses 的另一个区别是引脚访问。用于 Pins 属性和初始化的是 OnInitializePins
例程,它在 BaseFilter
类中是抽象的。
protected abstract int OnInitializePins();
除了引脚初始化,你还可以通过以下辅助例程动态操作引脚:
public int AddPin(BasePin _pin)
public int RemovePin(BasePin _pin)
注册过滤器在 GraphEdit
中的样子
要创建自己的过滤器,只需继承自任何基过滤器类,指定必需的属性并添加功能即可。
自定义过滤器注册
在某些情况下,可能需要执行自定义过滤器注册,无论是否包含基础注册。为此,添加了一些方法。例如,你可以使用这些方法来为你的分离器/源过滤器注册文件扩展名。
protected virtual int BeforeInstall(ref RegFilter2 _reginfo,ref IFilterMapper2 _mapper2)
{
return NOERROR;
}
protected virtual int AfterInstall(HRESULT hr,ref RegFilter2 _reginfo, ref IFilterMapper2 _mapper2)
{
return NOERROR;
}
protected virtual int BeforeUninstall(ref IFilterMapper2 _mapper2)
{
return NOERROR;
}
protected virtual int AfterUninstall(HRESULT hr, ref IFilterMapper2 _mapper2)
{
return NOERROR;
}
你可以在注册过滤器之前重新配置 _reginfo 变量,并在 BeforeInstall 方法中为某些字段分配内存。并在 AfterInstall 方法中释放分配的内存。如果 BeforeInstall 返回失败 - 过滤器未注册,AfterInstall 不会被调用。AfterInstall 接收注册的 HRESULT 值,因此你可以检查过滤器是否成功注册并执行其他注册功能。
属性页
创建过滤器属性页也非常简单。你应该创建一个 Window Form 并让它继承自 BasePropertyPage
类,而不是 Windows.Form
。你创建的窗体也需要 Guid
属性,因为这也是 COM 对象。BasePropertyPage
类具有与原生 BaseClasses 中的基类相同的可覆盖方法。要将你的属性页分配给过滤器,你应该在你的过滤器上指定一个属性。该属性看起来像这样:
[ComVisible(true)]
[AttributeUsage(AttributeTargets.Class)]
public class PropPageSetup : Attribute
//........
public PropPageSetup(Guid _guid)
public PropPageSetup(Guid _guid1,Guid _guid2)
public PropPageSetup(Guid _guid1,Guid _guid2,Guid _guid3)
public PropPageSetup(Type _type)
public PropPageSetup(Type _type1, Type _type2)
public PropPageSetup(Type _type1,Type _type2,Type _type3)
该属性有几个构造函数。指定属性页到过滤器的示例:
[ComVisible(true)]
[Guid("701F5A6E-CE48-4dd8-A619-3FEB42E4AC77")]
[AMovieSetup(true)]
[PropPageSetup(typeof(AudioChannelForm),typeof(AboutForm))]
public class AudioChannelFilter : TransformFilter, IAudioChannel
AudioChannelFilter
的属性页外观
文件分离器和文件源实现
在这一部分,我将简要概述类库,因为原生 BaseClasses 中没有类似的东西,但这可以帮助你实现现有格式的解复用器,甚至创建你自己的媒体文件格式。解复用器的基类是 BaseSplitterFilter
和 BaseFileSourceFilter
,选择哪一个取决于你所需的功能。大多数情况下,你不需要修改这些基类。但是对于你的文件格式,你应该至少创建 2 个类,并让它们继承自 FileParser
和 DemuxTrack
。
FileParser
- 用于执行文件检查和初始化轨道的基类。在此,你应该重写至少两个方法。CheckFile
- 在这里,你应该验证你是否可以解析给定的文件或流。**注意**,该方法在文件或流实现时都会被调用,如果是文件源,第一次调用时只初始化了 m_sFileName
变量,第二次(如果第一次失败)调用时则使用 m_Stream
,这是一个 BitStreamReader
类的对象。另一个需要重写的方法是抽象的 LoadTracks
- 在这里,你应该初始化轨道(继承自 DemuxTrack
的类)并将它们放入 m_Tracks
列表中。在此方法中,你还可以初始化文件持续时间(以纳秒为单位)(m_rtDuration
变量)。解析工作分为 2 种模式:单解析线程模型 - 存在一个主线程,并在其中执行解复用;多线程解析模型 - 在此,每个引脚负责解析数据。**注意**:访问位流以读取数据是线程安全的。要指定使用哪种模式,请在 FileParser
构造函数中指定布尔参数 - bRequireDemuxThread
- true 表示单线程模型(默认)。关于这 2 种模式以及如何选择,我计划在另一篇文章中描述,这里只进行实现概述。
单线程模型 - 要使用此模型,你应该在 FileParser
类中重写另外 2 个方法。SeekToTime
- 用于支持查找 - 在此方法中,你应该根据指定的时间设置读取位置。ProcessDemuxPackets
- 用于解复用数据并将其放入轨道队列的主要方法。在此,你应该创建一个 PacketData
对象,用数据填充它,并通过调用 AddToCache
方法将其放入 DemuxTrack
。**注意**:一旦队列已满,此方法将阻塞直到样本传递到过滤器。**注意**:如果存在关键的非阻塞来避免挂起,则 DemuxTrack
类中有一个 Reset
方法,并且所有线程都会因在 BaseSplitter
中设置退出事件而自动退出。但我建议你不要手动使用任何这些,因为这些都由类处理。
多线程模型 - 在此模型中,每个轨道读取和传递数据,并且可能不使用队列。要实现此模型,你应该在继承自 DemuxTrack
的类中重写至少 2 个方法:GetNextPacket
- 返回填充的 PacketData
对象,或者在 EOS 的情况下返回 null
;以及 SeekToTime
- 与单模型中的目的相同,但在此处直接在每个轨道上执行查找。
DemuxTrack
- 每个轨道的基类。你应该重写其抽象方法 GetMediaType
。其他方法可以根据你选择的模型或特定的轨道实现进行重写。
PacketData
- 用于指定轨道媒体数据的基类。它可以包含实际数据或指向文件位置的指针。无论如何,你可以为你自己的提供媒体数据的方式重写此类,在这种情况下,你应该重写 Dispose
方法来清理资源,以及可能在 DemuxTrack
类中重写 FillSampleBuffer
方法以从你的类填充媒体样本缓冲区。
PacketsQueue
- 单线程模型中使用的样本队列类。该类允许从队列添加和删除 PacketData
对象,并发出队列已满或为空的信号。队列可以按时间戳对样本进行排序 - 当你在过滤器中执行解复用和解码时会用到此功能。默认缓存大小为 2 秒 - 在引脚激活后分配。
BitStreamReader
- 用于从给定流读取数据的类。辅助类允许线程安全地按对象、位、字节或 golomb 值读取数据。
在示例中有 2 个过滤器向你展示了解复用器实现的基础知识:WAVESplitterFilter
和 WAVESourceFilter
。但它极大地简化了创建你自己的类,我计划在后续文章中更详细地描述这些类,例如,我制作了一个 Windows Media Splitter,在下一张图中可以看到(是的,单个文件中存在 4 个流 - 只是与我自己的多路复用器一起操作 - 但分离器可以轻松处理)。
将过滤器参数保存在注册表中
为了更好地根据之前设置的参数配置过滤器,存在两个辅助方法。它们允许你从系统注册表中读取或写入字符串或数值参数。这些方法位于 BaseFilter
类中。
// Retrieve value from registry for current filter
protected object GetFilterRegistryValue(string _name,object _default)
// Set registry value for current filter
protected bool SetFilterRegistryValue(string _name,object _value)
参数存储在过滤器注册表子键下,因此是个人过滤器数据。**注意**:所有设置在过滤器注销时都会被删除。
将过滤器参数保存在图文件中
除了将参数保存在注册表中,过滤器还允许从保存的图中持久化保存和加载数据。每个过滤器都支持 IPersistStream
接口,并提供一些辅助方法来使用它。
// Set or clear flag that properties are modified
protected HRESULT SetDirty(bool bDirty)
// Get's the size of persistent data
protected virtual long SizeMax()
// Write filter properties to the stream
protected virtual HRESULT WriteToStream(Stream _stream)
// Read filter properties from stream
protected virtual HRESULT ReadFromStream(Stream _stream)
要标记持久化数据已修改,你应该使用 SetDirty
方法。加载和保存信息的参数是 .NET 类型 System.IO.Stream
。**注意**:如果你不重写 SizeMax
方法,则流写入可能会执行两次:第一次计算最大输出大小,第二次实际写入数据。在你的过滤器实现中,你应该至少重写 WriteToStream
和 ReadFromStream
方法。
将过滤器嵌入到应用程序中
你可能不知道过滤器可以在应用程序内部创建并插入到过滤器图中。为此,过滤器不注册到注册表中,只有你的应用程序才能使用它。这也可以在 .NET 中实现。如果你的过滤器位于将注册为 COM 库的程序集中,则使你的过滤器带有 [ComVisible(false)]
属性,在应用程序中你可以不使用此属性。此外,我们不需要任何其他注册属性,因此可以删除所有这些,甚至 Guid
属性,因为过滤器注册将不会被调用。请记住,在这种情况下,持久化数据保存将无法工作,包括注册表值。如果需要属性页,它们应该注册为 COM,但我不能确定嵌入过滤器是否需要属性页,因为你可以在应用程序中手动配置所有设置。应用程序中过滤器声明的示例:
public class WAVESourceFilter : BaseSourceFilterTemplate<WaveParser>
{
public WAVESourceFilter()
: base("CSharp WAVE Source")
{
}
}
一旦过滤器类准备好,你就可以将其创建为特定的 .NET 对象。由于过滤器支持 IBaseFilter 接口,你可以将其插入到 Filter Graph 中并无问题地使用它。
public class WavPlayback : DSFilePlayback
{
protected override HRESULT OnInitInterfaces()
{
// Create Filter
DSBaseSourceFilter _source = new DSBaseSourceFilter(new WAVESourceFilter());
// load the file
_source.FileName = m_sFileName;
// Add to the filter Graph
_source.FilterGraph = m_GraphBuilder;
// Render the output pin
return _source.OutputPin.Render();
}
}
在下载的示例应用程序中有 3 个示例(WavCapture、WavExtract 和 WavPlay),它们展示了如何在应用程序中使用嵌入的过滤器。
DirectShowNET 库
BaseClasses **不**使用 DirectShowNET 库,它包含一些相同的接口和结构,但封送方式不同。这是必要且非常重要的,因为在许多情况下,我们需要 IntPtr
而不是实际的接口,以避免我之前文章中描述的线程问题。通过我的魔法类(也来自之前的文章)VTableInterface
访问实际接口。我对其进行了一些修改并增加了更多功能。你也可以在过滤器中使用 DirectShowNET 库,但要记住可能出现的模糊问题。我将我的 directshow 辅助类(用于方便地构建过滤器图)作为单独的可下载内容放在本文中,而不是 DirectShowNET 库。
示例过滤器
NullInPlaceFilter
示例原地变换过滤器,它不修改数据,可以用作开发过滤器的起点。
NullTransformFilter
示例变换过滤器,它仅复制媒体样本而不进行修改。此示例可用作开发自己的变换过滤器的起点。
DumpFilter
示例过滤器,用于将传入数据写入指定文件。过滤器支持 IFileSinkFilter
和 IAMFilterMiscFlags
。过滤器接受任何传入类型。
TextOverFilter
示例变换过滤器,演示如何在视频流上绘制文本。接受的媒体类型为 RGB32,过滤器不执行间距校正,将通过 ColorSpace Converter 连接到视频渲染器。
VideoRotationFilter
示例变换过滤器,将视频旋转 90 度。接受的媒体类型为 RGB24,过滤器不执行间距校正,将通过 ColorSpace Converter 连接到视频渲染器。
ImageSourceFilter
示例实现推送源过滤器,将加载的图像文件作为视频流提供。过滤器支持 IFileSourceFilter
接口。输出宽度和高度根据加载的图像分辨率设置。默认 FPS 为 20。输出媒体格式为 RGB32。过滤器不执行间距校正,将通过 ColorSpace Converter 连接到视频渲染器。
ScreenCaptureFilter
示例推送源过滤器,通过 GDI 提供当前桌面图像的副本。输出宽度和高度设置为 640x480,过滤器执行缩放以适应该分辨率。默认 FPS 为 20。输出媒体格式为 RGB32。过滤器不执行间距校正,将通过 ColorSpace Converter 连接到视频渲染器。
WavDestFilter
示例过滤器,它将音频流写入 WAV 文件。过滤器支持 IFileSinkFilter
和 IAMFilterMiscFlags
。过滤器接受 PCM 媒体类型和 WaveFormatEx
格式类型。
AudioChannelFilter
示例变换过滤器,它将音频输出到指定的通道。过滤器支持 WAVE_FORMAT_PCM
格式的 PCM 输入和 8 或 16 位的 BitsPerSample
。输出媒体格式为 WaveFormatExtensible
,具有一个通道输出和扬声器配置。过滤器支持自定义接口,通过使用它可以指定输出通道。
public enum AudioChannel : int
{
FRONT_LEFT = 0x1,
FRONT_RIGHT = 0x2,
FRONT_CENTER = 0x4,
LOW_FREQUENCY = 0x8,
BACK_LEFT = 0x10,
BACK_RIGHT = 0x20,
SIDE_LEFT = 0x200,
SIDE_RIGHT = 0x400,
}
[ComVisible(true)]
[System.Security.SuppressUnmanagedCodeSecurity,
Guid("29D64CCD-D271-4390-8CF2-89D445E7814B"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAudioChannel
{
[PreserveSig]
int put_ActiveChannel([In] AudioChannel _channel);
[PreserveSig]
int get_ActiveChannel([Out] out AudioChannel _channel);
}
InfTeeFilter
示例类似于 Microsoft InfTee 示例 DirectShow 过滤器。
Null Renderer Filter
过滤器继承自 BaseRendererFilter
,并且工作方式与标准的“Null Renderer”相同 - 通过丢弃所有接收到的样本而无需显示或渲染它们。
WAVE Splitter Filter
分离器过滤器示例,它解析 WAVE 文件数据并将其传递给下游过滤器。过滤器有一个输入引脚,并支持连接到带有 IAsyncReader
接口的引脚(例如“File Source Async”过滤器)。输出引脚在输入引脚连接后创建。过滤器不会自动注册使用,因此您需要手动构建图。
WAVE Source Filter
源过滤器示例,它解析 WAVE 文件数据并将其传递给下游过滤器。过滤器支持 IFileSourceFilter
并且没有输入引脚。输出引脚在输入引脚连接后创建。过滤器不会自动注册使用,因此您需要手动构建图。
Network Sync 和 Source Filters
示例是 2 个过滤器:发送方和接收方,它们将压缩为 JPEG 的视频数据进行组播。一个 Sync 过滤器可以连接到多个源过滤器。Sync 过滤器继承自 BaseRendrerFilter
并支持 RGB24 输入。一旦图变为活动状态,sync 过滤器就开始组播传入的样本。可以通过 INetworkConfig
接口配置组播设置。
[ComVisible(true)] [System.Security.SuppressUnmanagedCodeSecurity] [Guid("96D8A0B7-8EE7-4325-98D4-BB7C66F27B1A")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface INetworkConfig { string IP { get; set; } int Port { get; set; } }
该接口允许指定组播 IP 地址和端口。在 GraphEdit 中,你可以使用属性页进行此操作。
源过滤器一旦添加到过滤器图中,就会在一个单独的线程中开始等待样本。仅当至少一个样本被接收后,输出引脚上的 MediaType 才可用。过滤器还支持 INetworkConfig
接口。**注意**,由于要发送的数据可能很大且在系统级别不受支持,过滤器可能无法在高分辨率下工作。
高级示例
我将作为单独的文章发布更多示例过滤器,因为它们需要审查代码。目前可用的示例包括:
- C# 中的 H.264 CUDA 编码器 DirectShow 过滤器
- C# 中的 DirectShow 虚拟视频捕获源过滤器
- 用 C# 编写 DirectShow 解复用器。第 1 部分 - Windows Media Splitter 示例。
历史
- 2012-07-13 - 初始版本。
- 2012-07-15 - 添加了属性页、OutputQueue 和 InfTeeFilter 示例。
- 2012-08-09 - 进行了一些改进。
- 2012-08-14 - 实现了 BaseRenderer、BaseSplitter 和 BaseFileSource,并添加了几个示例。
- 2012-09-19 - 添加了应用程序示例。
- 2012-10-13 - 解决了 .NET Framework 2.0 中丢失方法的问题。修改了类库以解决其他一些问题。