现在的位置: 首页 > 综合 > 正文

【转】How to call c++ exported method and classes in c#

2014年09月05日 ⁄ 综合 ⁄ 共 5491字 ⁄ 字号 评论关闭

      相对于C++来说,C#是个newcomer,C#提供了一些非常cool的功能,使得我们的工作更加容易。但是,在这个组件横行的年代,如果不懂利用别人写好的代码,就有点落伍了。在C++横行江湖的日子里,留下了很多功能强大的代码,探讨如何在C#的程序中使用这些代码是非常有趣也很有意义的事情,这也是我们今天这篇文章的主题。
C#是managed code,而C++不是,他们之间的几种交互方式可以用下图表示(今天我们只谈managed code 调用 native code):
让我们看上图,managed code调用unmanaged code可以采用两种方式,第一种是利用.NET的运行时交互功能(runtime interop facilities),也就是平常看得比较多的P/Invoke(Platform Invoke)或者说DllImport,这种方式用起来比较简单,性能不高,可以用于C风格的dll文件,也可以用于不太复杂的C++类;第二种是手动用managed c++ extensions 封装(wrap)unmanaged c++ class,然后转换成DLL文件,直接加入C#工程的引用集(Refercences)。下面逐条讨论:
第一是使用.NET运行时交互功能的P/Invoke 方式,这又分两个子类:
1. unmanaged code是纯C风格的文件,则直接DllImport即可。
/*Word.dll中有定义好的函数getWord(LPTCHR InputWord)*/
using System.Runtime.InteropServices;//此句必须要,DllImport的namespace
[DllImport("Word.dll",
EntryPoint="getWord",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
private static extern string getWord(string lex);//declare
public static string getWord(string InputWord)
{
return getWord(InputWord);
}
/*备注:这样就可以了,关于DllImport详细的属性设置或者说明,可以参照MSDN,需要注意的就是设置好与Unmanaged code相对应的数据类型。*/
2. unmanaged code是带有C++类的文件
一般的做法就是使用托管代码对非托管(unmanaged)C++类进行封装,也就是为其创建一个Wrapper,大致步骤如下:
a) 得到这个非托管类的源代码,这是必需的;
b) 使用C#或者其他任何.NET托管语言创建一个非托管C++类的实例,是不可能办到的。但是要解决这个问题,就需要进行变通。解决方法就是对这个C++类进行扩展,为其创建一个工厂方法(method)(一个静态方法),这个方法必须能够像上面所述的C#调用DLL中的函数那样,能在不创建这个类的实例的前提下,直接被托管代码调用。
c) 使用C#或者其他任何.NET托管语言调用一个非托管C++类的析构函数,也是不可能办到的。因此也需要相应地为这个非托管C++类创建一个可导出供C#调用的静态方法,这个方法的主要任务就是销毁(destroy)类的实例。
d) 对于导出的这个方法,由于不能直接用做Entry point,因此需要使用ordinal作为Entry Point,但是ordinal会随着导入函数的添加或者移除而发生改变,因此使得维护变得比较困难,也容易出现错误。
e) 像上面所述的那样,使用DllImport调用上面实现的导出函数。
这种方法提供了一种比较好的解决方案。我们给个实例
C++ head file
#ifdef PINVOKELIB_EXPORTS
#define PINVOKELIB_API __declspec(dllexport)
#else
#define PINVOKELIB_API __declspec(dllimport)
#endif
// This class is exported from the PinvokeLib.dll
class PINVOKELIB_API CTestClass
{
public:
CTestClass( void );
int DoSomething( int i );
private:
int m_member;
};
extern "C" PINVOKELIB_API CTestClass* CreateTestClass();
extern "C" PINVOKELIB_API void DeleteTestClass( CTestClass* instance );
C++ Body file
#include "stdafx.h"
#include "PinvokeLib.h"
#include <objbase.h>
#include <stdio.h>

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{ switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
// This is the constructor of a class that has been exported.
CTestClass::CTestClass()
{
m_member = 1;
}
int CTestClass::DoSomething( int i )
{
return i*i + m_member;
}
extern "C" PINVOKELIB_API CTestClass* CreateTestClass()
{
return new CTestClass();
}
extern "C" PINVOKELIB_API void DeleteTestClass( CTestClass* instance )
{
delete instance;
}
当使用C# Client调用时,我们只需要编译上述C++文件成DLL,然后使用DllImport即可,如下:
C# code file
using System;
using System.Text;
using System.Runtime.InteropServices;

public class LibWrap
{
[ DllImport( "PinvokeLib.dll",
EntryPoint="?DoSomething@CTestClass@@QAEHH@Z",
CallingConvention=CallingConvention.ThisCall )]
public static extern int TestThisCalling( IntPtr ths, int i );
// CTestClass* CreateTestClass();
[ DllImport( "PinvokeLib.dll" )]
public static extern IntPtr CreateTestClass();
// void DeleteTestClass( CTestClass* instance )
[ DllImport( "PinvokeLib.dll" )]
public static extern void DeleteTestClass( IntPtr instance );
}
public class App{
public static void Main() {
try {
IntPtr instancePtr = LibWrap.CreateTestClass();
int res = LibWrap.TestThisCalling( instancePtr, 9 );
Console.WriteLine( "/nResult: {0} /n", res );
LibWrap.DeleteTestClass( instancePtr );
}
catch(Exception ex) {
Console.Write(ex.ToString());
}
}
}
以上源文件见http://www.cqumstc.net/bbs/dispbbs.asp?boardid=31&id=420
采用wrap方式直接对C++类进行封装,过程如下:
1. 获得unmanaged c++ code
2. 使用managed c++ 对其进行封装(wrap),即使用Win32 DLL将C++ DLL中的导出类进行改写,主要要解决的问题是构造构造函数和析构函数的实现,如下:
首先给出要导出供C#使用的C++类的原型:
C File a.c
/*这是一个基本的C函数,实现了最基本的功能,由b.cpp对他进行封装。 */
char *Word(char *);
char *dir;
int read_dict_info();
/* 注意:在调用Word()之前,必须首先给dir变量赋值,接着才能调用read_dict_info()函数将dir文件夹下面的文件读入。*/
C++ file b.cpp(这个文件里面包含了我们的需要的导出类)
// 一些头文件定义
extern char *Word(char *);
extern char *dir;
extern int read_dict_info();
ClassB::ClassB(string &datadir)
{
dir= new char[datadir.length() + 1];
strcpy(dir, datadir.c_str());
if (read_dict_info())
{
cerr << "Failure reading data files. Check " <<datadir<< endl;
}
}
ClassB::~ClassB() {
delete[](dir);
}
char * ClassB::GetWord(char * word)
{
if (islower(*word))
{
return Word(word);
}
}
按照上面提到的思路,如果要在C#中调用这个类,在不能对ClassB进行实例化的情况下,就需要使用Managed C++对这个Unmanaged C++进行wrap,然后再在C#中调用这个wrapper。
采用本文提到的思路,就可以这样做,具体实现的代码如下: (假设编译出来的DLL名称为Word.dll)
Managed c++ file
//B.h
extern "C" __declspec(dllexport) char * GetWord(char * word);
//B.cpp
// include your header files
extern char *Word(char *);
extern char *dir;
extern int read_dict_info();
char * GetWord(char * word)
{
return Word(word);
}
BOOL APIENTRY DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if( dwReason == DLL_PROCESS_ATTACH ) //等价的构造函数
{
char Datadir[2048];
GetCurrentDirectory(2048,Datadir);
strcat(Datadir, "//dataFiles");
int len=strlen(Datadir);
dir = new char[len+1];
strcpy(dir, Datadir);
if ( read_dict_info())
{
cerr << "Failure reading data files. Check " <<Datadir<< endl;
}
}
if( dwReason == DLL_PROCESS_DETACH ) //等价的析构函数
{
delete[](dir);
}
return true;
}
/*备注:DLL_PROCESS_ATTACH: 进程被调用、DLL_THREAD_ATTACH: 线程被调用、DLL_PROCESS_DETACH: 进程被停止、DLL_THREAD_DETACH: 线程被停止;因此,上面的代码就是利用DLL被调用或者停止时进行构造和析构操作。*/
然后,在C#里面,就可以这样进行调用:
C# File
[DllImport("Word.dll", EntryPoint="GetWord", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.StdCall)]
private static extern string GetWord(string Term);
public static string GetWord(string Term)
{
return GetWord(Term);
}
/*备注:注意对CharSet的设置,如果设置错误,就可能得不到正确的结果。*/

参考文献
1. 在VC编写的DLL中导出C++类
2. HOWTO: Combine managed and Unmanaged Projects into a Single Visual Studio Solution
3. Calling Managed Code from Unmanaged Code and vice-versa
4. Discussion List

【转载:http://www.mscenter.edu.cn/zhuanti/dianziqikan/15/childpage/10.htm

抱歉!评论已关闭.