综述
C# 调用 C++ 中的函数或类是通过调用其 dll 来实现的。对于 unmanaged C++,我们在每个函数前加上 dllexport
,并在 C# 代码中通过 dllimport
和 extern static
来调用 dll 中的函数。这样需要为每个函数添加 dllimport/dllexport
,很不方便。注意 C# 是可以直接调用 C++/CLR (managed C++) 的 dll 的。因此,为了两端(调用端/实现端)代码编写的便利,我们可以用 C++/CLR 作为联系 unmanaged C++ 和 C# 的 wrapper,写出更简洁的代码。
C# 调用 C++ 代码 —— dllimport 方式
编写 C++ DLL
新建 VC++ 控制台空项目,项目类型选 DLL,新建 MathFuncDll.h 和 MathFuncDll.cpp 文件,代码如下:
// MathFuncsDll.h
#include <stdexcept>
using namespace std;
namespace MathFuncs
{
extern "C" { __declspec(dllexport) double Add(double a, double b); }
extern "C" { __declspec(dllexport) double Subtract(double a, double b); }
extern "C" { __declspec(dllexport) double Multiply(double a, double b); }
extern "C" { __declspec(dllexport) double Divide(double a, double b); }
}
// MathFuncsDll.cpp
#include "MathFuncsDll.h"
namespace MathFuncs
{
double Add( double a, double b )
{ return a+b; }
double Subtract( double a, double b )
{ return a-b; }
double Multiply( double a, double b )
{ return a*b; }
double Divide( double a, double b )
{
if ( b == 0 )
throw invalid_argument("b cannot be zero!");
return a/b;
}
}
编译后在 Debug 或 Release 文件夹中可找到 MathFuncsDll.dll 文件。
编写 C# 测试程序
新建 C# 控制台项目,Program.cs 的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace cppdll_test_csharp
{
class Program
{
[DllImport("C:/Users/huanglianghua/Documents/Visual Studio 2012/Projects/cppdll_test/Debug/cppdll_test.dll",
CallingConvention = CallingConvention.Cdecl)]
public static extern double Add(double a, double b);
static void Main(string[] args)
{
double c = Add(10, 10);
Console.WriteLine(c);
}
}
}
运行后输出 20
代表程序正确。
C# 调用 C++ 代码 —— C++ wrapper 方式
以上代码能够正确运行。但是需要 dllimport/dllexport
,且调用时每个函数需要定义 static extern
。当函数数量不多时,这种方法容易使用。但当函数数量很大时,调用会很不方便。
注意到虽然 C# 调用普通的 C++ 代码(unmanaged C++)很麻烦,但 C# 是可以直接调用 C++/CLI (managed C++) 的 dll 的。C++/CLI 即托管的 C++ —— 以 C++ 的名义写 C#。CLI 是 Common Language Infrastructure 的缩写。所以我们可以将 C++/CLI 作为 C++ 和 C# 的接口 —— 为 C++ 程序写一个 wrapper,然后在 C# 里直接调用。
简而言之,需要三个项目:
- 一个 VC++ 项目,采用 unmanaged C++,编写主要的函数体;
- 一个 VC++ 项目,采用 managed C++,编写 C++/CLI wrapper;
- 一个 VC# 项目,测试 C++/CLI wrapper 的 dll。
C++ 主体代码
建立 VC++ 空项目,项目类型为控制台应用程序,建立 MathFuncs.h, MathFuncs.cpp 和 main.cpp 文件,其中 main.cpp 主要用于测试 MathFuncs.cpp 中的函数。代码如下:
// MathFuncs.h
#pragma once
#include <stdexcept>
using namespace std;
class MyMathFuncs
{
public:
double Add(double a, double b);
double Subtract(double a, double b);
double Multiply(double a, double b);
double Divide(double a, double b);
};
// MathFuncs.cpp
#pragma once
#include "cli_cpp.h"
double MyMathFuncs::Add(double a, double b)
{ return a+b; }
double MyMathFuncs::Subtract(double a, double b)
{ return a-b; }
double MyMathFuncs::Multiply(double a, double b)
{ return a*b; }
double MyMathFuncs::Divide(double a, double b)
{
if ( b == 0 )
throw invalid_argument("b cannot be zero!");
}
// main.cpp
#include <stdio.h>
#include "cli_cpp.h"
using namespace std;
int main()
{
MyMathFuncs* m = new MyMathFuncs();
double c = m->Add (10.1,20.2) ;
printf("Result is %f\n", c);
free(m);
return 0;
}
代码无误的话运行会出现 “Result is 30.300000”。
C++ wrapper 代码
新建 VC++ CLI Class Library 项目 managedDllWrapper,修改 managedDllWrapper.h 和 managedDllWrapper.cpp 如下:
// managedDllWrapper.h
#pragma once
#include "C:\Visual Studio 2012/Projects/MathFuncs/MathFuncs.h"
#include "C:\Visual Studio 2012/Projects/MathFuncs/MathFuncs.cpp"
using namespace System;
namespace managedDllWrapper {
public ref class MyMathFuncsWrapper
{
public:
// constructor
MyMathFuncsWrapper();
// wrapper methods
double AddWrapper ( double a, double b);
double SubtractWrapper ( double a, double b);
double MultiplyWrapper ( double a, double b);
double DivideWrapper ( double a, double b);
// public variable
double initVal;
private:
MyMathFuncs* myCppClass; // an instance
};
}
// managedDllWrapper.cpp
#include "stdafx.h"
#include "managedDllWrapper.h"
#include "C:\Visual Studio 2012/Projects/MathFuncs/MathFuncs.h"
#include "C:\Visual Studio 2012/Projects/MathFuncs/MathFuncs.cpp"
// Constructor implementaion
managedDllWrapper::MyMathFuncsWrapper::MyMathFuncsWrapper()
{
initVal = 20.0;
myCppClass = new MyMathFuncs(); //initiate C++ class's instance
}
double managedDllWrapper::MyMathFuncsWrapper::AddWrapper ( double a, double b)
{
return myCppClass->Add(a,b);
}
double managedDllWrapper::MyMathFuncsWrapper::SubtractWrapper (double a, double b)
{
return myCppClass->Subtract(a,b);
}
double managedDllWrapper::MyMathFuncsWrapper::MultiplyWrapper (double a, double b)
{
return myCppClass->Multiply(a,b);
}
double managedDllWrapper::MyMathFuncsWrapper::DivideWrapper (double a, double b)
{
return myCppClass->Divide(a,b);
}
以 Debug 或/和 Release 模式编译后会生成 managedDllWrapper.dll 文件。我们将在 C# 中使用它们。
C# 测试代码
新建 C# 控制台应用程序,在 References
中右键添加,选择 Browse...
找到 managedDllWrapper.dll 并添加,即可直接在 C# 代码中使用 dll 中的函数。C# 测试代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using managedDllWrapper;
namespace testCSharpInterface
{
class Program
{
static MyMathFuncsWrapper mymath = new MyMathFuncsWrapper();
static void Main(string[] args)
{
double c = mymath.initVal;
Console.WriteLine(c);
c = mymath.AddWrapper(c, c);
Console.WriteLine(c);
}
}
}
运行无误应该出现结果 20
和 40
。
C# 调用 C++ OpenCV 代码
在 rich UI 的 C# 中调用 rich efficiency 的 C++ OpenCV 代码,结合二者的优势。