C#与C++的互操作性(interoperate)

发布于:2021-10-28 03:21:44

你能告诉我如何从 C# 中调用 Visual C++ 类,对此我需要什么样的语法?

Sunil Peddi

我有一个用 C#(用户界面)和经典的 C++(业务逻辑)写的应用程序。现在我需要从某个用 C++ 写的 DLL中调用一个函数(或方法),该函数在一个用 Visual C++ .NET 编写的 DLL 中。而这个? Visual C++ .NET DLL 又要调用另一个用 C# 写的 DLL。Visual C++ .NET DLL 相当于一个代理。这样做可行吗?我能用 LoadLibrary 调用 Visual C++ .NET DLL 输出的函数,可以得到返回值,但当我试图向 Visual C++ .NET DLL 中的函数传递参数时,我遇到如下错误:

Run-Time Error Check Failure #0?The value of ESP was not properly saved
across a function call. This is usually a result of calling a function
declared with one calling convention with a function pointer declared
with a different calling convention.

我如何解决这个问题?


Giuseppe Dattilo



  为了使用托管扩展,你只需引入 和你打算使用的框架类所附着的程序集。不要忘了用 /clr 编译。


cl /clr hello.cpp

  你的 C++ 代码可以或多或少地使用托管类,就像普通的 C++ 类一样。例如,你可以用操作符? new 创建框架对象,并用 C++ 指针语法存取它们,象下面这样:


DateTime d = DateTime::Now;
String* s = String::Format("The date is {0}/n", d.ToString());
Console::WriteLine(s);
Console::WriteLine(s->Length);

  这里,String s 被声明为 String 指针,因为 String::Format 返回一个新的 String 对象。

public class Win32 {
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, int type);
}

  这段代码告诉编译器 MessageBox 是 user32.dll 中的一个函数,参数是 IntPtr (HWND),两个 String 和一个 int。这样你便可以在 C# 程序中调用:


Win32.MessageBox(0, "Hello World", "Platform Invoke Sample", 0);

  当然,使用 MessageBox 你不必通过 P/Invoke,因为 .NET 框架已经具备一个 MessageBox 类,但是大量的 API 函数框架是不直接支持的,调用这些函数时需要 P/Invoke。并且,你还可以用 P/Invoke 调用自己 DLL中输出的 C 函数。尽管在例子中我用的是 C#,但 P/Invoke 支持任何基于 .NET 的语言,如:Visual Basic .NET 或 JScript.NET。函数名称都相同,只是语法有差别。

public __gc class Widget
{
private:
CWidget* m_pObj; // ptr to native object
public:
Widget() { m_pObj = new CWidget; }
~Widget() { delete m_pObj; }
int Method(int n) { return m_pObj->Method(n); }
// etc.
};

任何类都是这种模式:


写一个托管类(__gc)保存一个指向本地类的指针; 编写构造函数和析构函数分配和销毁对象实例; 编写对应于 C++ 成员函数的包装器方法;

你不必包装所有的成员函数,仅仅包装那些打算暴露给托管环境的函数即可。

// C# client
MPerson.Person p = new MPerson.Person("Fred");
String name = p.Name;
p.Name = "Freddie";

  用不用属性纯粹是编程风格问题,我完全可以照搬本地 C++ 类的做法也输出两个方法:GetName 和 SetName。但属性给人的感觉更像 .NET。包装器类就是一个程序集,只不过与本地 DLL 链接。这是托管扩展一个很酷的特性之一:你可以直接与本地 C/C++ 代码链接。如果你下载并编译我的 CPerson 例子源代码,你会发现 makefile 产生两个单独的 DLLs:person.dll 和 mperson.dll,前者实现常规的本地 DLL,后者是包装前者的托管程序集。还有两个测试程序:testcpp.exe,此为调用 person.dll 的本地 C++ 程序;testcs.exe,此为用 C# 编写的程序,它调用托管包装器 mperson.dll(它又调用本地 person.dll)。

// PtrToStringChars, from vcclr.h

// get an interior gc pointer to the first character contained in a
// System::String object
//
inline const System::Char * PtrToStringChars(const System::String *s) {
const System::Byte *bp = reinterpret_cast(s);
if( bp != 0 ) {
unsigned offset = System::Runtime::CompilerServices::
RuntimeHelpers::OffsetToStringData;
bp += offset;
}
return reinterpret_cast(bp);
} 我在 MPerson 中使用 PtrToStringChars 来设置 Name,详细代码参见
Figure 3。


  正像你已经明确注意到的,还有一个 gcnew 操作符用以来表示你是在托管堆中分配对象,而不是在本地分配。这样做有一个额外的好处是 gcnew 不会与 C++ 的 new 发生冲突,它能被重载或者甚至被重定义成一个宏。C++/CLI 有许多其它很棒的特性,专门用来使互用性尽可能简单明了。
?

相关推荐

最新更新

猜你喜欢