管理非托管代码






4.67/5 (16投票s)
托管 C++ 包装器,
介绍
将用原生 C++ 编写的遗留系统与 .NET 应用程序集成,最好的方法是什么? 首先想到的是 COM。是的,我们当然可以通过 COM 暴露原生 C++ 中的方法。 但处理 COM 并非易事。总有一个问题,**内存泄漏**。查找和修复这些泄漏非常痛苦。通过 COM,你只能暴露一组方法,那 OOA(面向对象分析)在哪里?有没有办法将这些方法分组为一个类? 我只是在朝这个方向探索。
那么,如果 C++ 需要通过 COM 与 .NET 语言交互,那么托管 C++ 或 C++/CLI 的目的是什么? 可以有一种方法来构建一个托管库,该库内部包装了一些原生 C++ 代码。因此,对于外部世界来说,它看起来像一个托管库,但内部包装了非托管代码。
问题陈述
如何创建这样一个 VC++ 库,它既可以被所有 .NET 语言使用,也可以被所有遗留语言(如原生 C++、Visual Basic 6、Visual FoxPro 等)使用。
解决方案
针对上述问题,有两个已知的解决方案:
- 通过 COM/COM+ 暴露所需方法。这是多年来最广泛使用的方法。
- 为原生 C++ 库编写一个托管 C++/CLI 包装器。是的,托管库可以轻松被所有 .NET 语言访问,但遗留语言呢?要让遗留语言访问,需要通过 COM 暴露方法。即使通过 COM 暴露方法是一种迂回的方式,但我假设该库将在 .NET 语言中更常用,并且这有助于维护性。
在本文中,我们将讨论第二种方法。
优点
- 在 C++ 中处理 COM 非常繁琐。
- 内存泄漏可以最小化,因为我们充分利用了 CLR。
- COM 通常速度较慢且难以维护。
- 方法可以分组为一个类,因此我们甚至可以用面向对象的概念来包装原生 C 代码。
第一步
那么我从哪里开始呢?我有一个原生的 C++ 应用程序示例。我想将其包装在一个托管类中。我们面临的主要障碍是处理参数**。** 操作过程如下:
- 使用托管数据类型获取参数
[System::String^, System::UInt32, etc]
- 将参数封送(Marshall)到非托管数据类型。
- 使用这些非托管参数调用非托管方法。
- 将返回值封送(Marshall)到托管数据类型。
- 返回托管参数。
我知道这可能会很繁琐,但如果你能承受这种痛苦,你将受益匪浅。
Using the Code
让我们来看一个简单的 C++/CLI 示例应用程序,它包装了一些原生 C++ 方法。类定义如下:
public ref class ManagedWrapperClass
{
private:
void MarshalString ( System::String ^ s, std::string& os );
public:
int ManagedAdd(int num1, int num2);
System::String^ ManagedConcat(System::String^ string1,System::String^ string2);
void ManagedCompareConcat(System::String^ string1, System::String^ string2,
[System::Runtime::InteropServices::Out]System::String^ %concatString,
[System::Runtime::InteropServices::Out] System::Int32 %result);
};
// The Class which is being wrapped
public class unmanagedClass
{
public:
unmanagedClass();
int UnManagedMethodAddNum(int num1, int num2);
std::string UnManagedMethodConcatString
(std::string string1,std::string string2);
void UnManagedMethodCompareConcat(std::string string1,
std::string string2, std::string* concatString, int *result);
};
在这里,我在 C++/CLI 中定义了一个类,它公开了三个方法。第一个方法ManagedAdd()
相对简单,它只是将两个整数相加并返回总和。如果你看第二个方法 (ManagedConcat()
),参数是System::String^
,这是一种 .NET 兼容的数据类型。它需要一些封送。第三个方法ManagedCompareConcat()
包装了一个包含一些OUT
参数的方法,而这些参数在 C++ 中基本上是指针变量。
int unmanagedClass::UnManagedMethodAddNum(int num1, int num2)
{
return num1+ num2;
}
std::string unmanagedClass::UnManagedMethodConcatString
(std::string string1,std::string string2)
{
return string1.append(string2);
}
void unmanagedClass::UnManagedMethodCompareConcat(std::string string1,
std::string string2, std::string * concatString, int *result)
{
//concatenating the string and storing it in the out parameter
std::string string3;
string3.append(string1);
string3.append(string2);
concatString->assign(string3);
int cmp = strcmp(string1.c_str(),string2.c_str());
*result = cmp;
}
包装器方法实现如下:
int ManagedWrapperClass::ManagedAdd(int num1,int num2)
{
unmanagedClass myClass ;
return myClass.UnManagedMethodAddNum(num1,num2);
}
System::String^ ManagedWrapperClass::ManagedConcat
(System::String ^string1, System::String ^string2)
{
//marshalling parameters to unmanaged type
std::string unmanagedString1, unmanagedString2;
MarshalString(string1,unmanagedString1);
MarshalString(string2,unmanagedString2);
unmanagedClass myClass;
//Invoking the unManagedmethod
std::string resultString =
myClass.UnManagedMethodConcatString(unmanagedString1,unmanagedString2);
//Marshalling return parameter back to managed type
System::String^ returnString = gcnew System::String(resultString.c_str());
return returnString;
}
void ManagedWrapperClass::ManagedCompareConcat(System::String^
string1, System::String^ string2,
[System::Runtime::InteropServices::Out]System::String^ %concatString,
[System::Runtime::InteropServices::Out] System::Int32 %result)
{
//marshalling parameters to unmanaged type
std::string unmanagedString1, unmanagedString2;
MarshalString(string1,unmanagedString1);
MarshalString(string2,unmanagedString2);
// invoking the unmanaged method
unmanagedClass myClass;
std::string resultString;
int resultInt;
myClass.UnManagedMethodCompareConcat
(unmanagedString1,unmanagedString2,&resultString,&resultInt);
//marshalling the OUT parameters
concatString = gcnew System::String(resultString.c_str());
result = resultInt;
}
//Method to marshal a CLR compatible System::String to a std::string
void ManagedWrapperClass::MarshalString ( System::String ^ s, std::string& os )
{
const char* chars =
(const char*)(Marshal::StringToHGlobalAnsi(s)).ToPointer();
os = chars;
Marshal::FreeHGlobal( System::IntPtr((void*)chars));
}
进行包装时,主要的障碍将是参数封送。但要找到将原生数据类型转换为 .NET 兼容数据类型的方法并不难。
第二步
通过上述实现,我能够生成一个 DLL,该 DLL 可在所有 .NET 语言中使用。 如何在其他遗留语言(如原生 C++、Visual Basic 6 或 Visual FoxPro)中使用同一个库?现在问题很简单。如何在非托管代码中消耗托管库。为此,最好的方法是通过 COM 暴露方法。 你可能会问我,为什么我需要一个托管包装器来访问原生 C++ 应用程序中的原生 C++ 应用程序。确实如此,我们有更简单的方法可以在非托管 C++ 应用程序中消耗非托管库。 但是,这迫使我们拥有两个不同的库或 DLL。一个是托管的;另一个是非托管的,用于执行相同的任务。有没有办法拥有一个可以跨平台使用的单一库? 是的,有一种方法。 在非托管代码中调用托管方法的最佳且最简单的方法是 COM。 因此,在这里我们将看到如何将 COM 部分添加到此库中。暴露方法到 COM 非常简单。唯一的补充是一个接口,这是暴露方法所必需的。现在类定义如下:
[ComVisible(true)]
[Guid("1DB44778-857F-47d1-9D4A-D90F109EFF35")]
public interface class IManagedWrapperClass
{
int ManagedAdd(int num1, int num2);
System::String^ ManagedConcat(System::String^ string1,System::String^ string2);
void ManagedCompareConcat(System::String^ string1,
System::String^ string2,
[System::Runtime::InteropServices::Out]System::String^ %concatString,
[System::Runtime::InteropServices::Out] System::Int32 %result);
};
[ComVisible(true)]
[ClassInterface(ClassInterfaceType::None)]
public ref class ManagedWrapperClass : public IManagedWrapperClass
{
private:
void MarshalString ( System::String ^ s, std::string& os );
public:
virtual int ManagedAdd(int num1, int num2);
virtual System::String^ ManagedConcat
(System::String^ string1,System::String^ string2);
virtual void ManagedCompareConcat(System::String^ string1,
System::String^ string2,
[System::Runtime::InteropServices::Out]System::String^ %concatString,
[System::Runtime::InteropServices::Out] System::Int32 %result);
};
// The Class which is being wrapped
public class unmanagedClass
{
public:
unmanagedClass();
int UnManagedMethodAddNum(int num1, int num2);
std::string UnManagedMethodConcatString(std::string string1,std::string string2);
void UnManagedMethodCompareConcat(std::string string1,
std::string string2, std::string* concatString, int *result);
};
现在我们有了一个库,所有 .NET 语言以及所有支持 COM 的遗留语言都可以使用它。
使用此库
在 .NET 语言中使用此库非常简单。将此 DLL 添加为项目的引用,即可开始使用。但在遗留应用程序中使用它需要做一些工作。要在原生 C++ 中使用它,我们必须使用以下命令创建一个 TLB 文件:
RegAsm /tlb:<filename.tlb> “filename.dll”
或者使用TlbExp
命令。
你可以使用#import
指令在你的 C++ 应用程序中使用此 TLB 文件。
结语
有许多库,它们拥有复杂的底层操作,如图像处理、网络等,仍然是原生 C++。希望这种方法能帮助我们将这些库包装成 .NET 数据类型,以实现无缝访问。