bindsang

工作五年,长期从事于asp.net方面的编程,业余爱好VC编程,温和、谦虚、自律、自信、善于与人交往沟通
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

C#与Native C++互相访问

Posted on 2008-12-22 14:23    阅读(1985)  评论(1编辑  收藏  举报

      用C#做开发已经好几年了,一直用得挺顺手的,最近有一个项目需要用到DirectShow的相关COM组件,也就是想在C#的项目里面实现一个基于内存流的Filter,这个却让我着实头痛了好久。

      原因就是在C#里面没有现成的DirectShow的COM组件的定义,虽说在C#中可以使用一些特定的方式来操作COM组件(C#中COM操作(一)---实例化),可是对于DirectShow这样需要声明一大批的COM接口,类型,枚举却是一件漫长又枯燥的事件,而且搞不好一个不小心中间出个错误,导致最后调试总不通过而抓狂。即便是现在有了DirectShow.NET提供的对绝大部分的DirectShow的C#翻译,可是面对网上那么多的C++开源代码还有少得可怜的C#操作DirectShow的代码,你会怎么办?难道又是一行一行的翻译成C#吗,反正我是不会这样干的,于是这种方式首先被毙掉了。

     俗话说最优秀的程序员也是最懒的程序员,类和方法能公用的就尽量公用,已存在的实现就不需要自己再去写一次了。既然有现成的C++的实现好的代码为什么不可以再次拿来用一用呢。可是在C#里面想要使用C++的实现(注意这里我用的是实现,并没有指定是类或方法),比较常见的是通过DllImportAttribute特性导入相应的程序集,可是这种方式只可以调用方法,而且是限定死了的只能是C语言形式的导出方法。对于其它的方法是不可以的,以前用的最多的就是对Windows API的调用了。这样的使用形式极其不方便,C++和C#本都是都面向对象的语言,却要通过面向过程式的方法调用来相互通信,感觉很别扭也很不爽,对于追求完美的程序员来说,这也不是一个上上的选择,于是这种方式也不是最好的,暂时作为候选吧,再找找看还有没有其它的方式。

      后来我想到了以前看过一篇文章说的是在C++里面通过托管C++作为媒价调用C#里的代码。把这个反过来就是用C#通过托管C++调用NativeC++这样是否也是可行的呢?不过这都是好几年前看到的了,只怪那时候对于这种跨平台调用的方式研究不深,也不怎么感兴趣所以当时就没有太在意,对于文中所说的托管C++的印象也一直停留在中看不中用的程序上。如今也只得找来看看,虽说信心不大,也报着死马当活马医一回的心情试试先。

      其实前面已经提到过了,如果仅仅是对C++的导出方法的调用的话基本不是难事,可是C++是面向对象的语言,很多情况下都是以类的形式存在的,导出的也是C++的类,就拿我找到的C++写的内存流的Filter来说吧,网上的代码都是基于类的,并且我想在C#里面控制FilterGraph的构建,解决怎么在C#里面直接或间接使用到这个类才是问题的本质和关键所在。网上找了几份资料,也查了MSDN,里面都说可以使用托管C++作Wrapper包装NativeC++然后编译成CLI可识别的托管程序集就可以供C#调用了。

 1 class MemFilter : public IBaseFilter
 2 {
 3    public:
 4 
 5    private:
 6 
 7 }
 8 
 9 public ref class MemFilterWrapper : IBaseFilter
10 {
11    private:
12       MemFilter* m_pFilter;
13    public:
14 // 此处只只需要把MemFilter类的公有方法用托管方工声明一下,在方法的实现中直接调用m_pFilter中相应的成员方法就可以了
15 ..
16 }

      把这两个类都编译到同一个CLI/C++项目中去,这样在C#里面就可以直接使用MemFilterWrapper这个类间接操作C++类了,与其它普通的CLS类没什么区别。不过需要注意的是对于C++里面的一些基元类型,比如int,char*,bool等,包括数组需要用CLS里面相应的类型去替换,然后再对外公布成方法,也就是说把所有的与C++特性相关的都封装起来,对外只显示符合公共语言规范的格式,不然的话在使用的时候比较麻烦(有可能需要使用C#的不安全代码)。

     就像之前提到的那样,我们也可以通过使用托管C++让C#的类披上一层C++的皮,再声明成导出类,这样就可以供普通C++调用了(前是你必须先安装Framework)。


 1 public class CSharpClass // C# 类
 2 {
 3 public void SayHello(){ Console.WriteLine("Hello"); }
 4 }
 5 
 6 class NativeCPP // C++原生类代理,注意这里的参数返回值什么的都不能带有托管类型的痕迹
 7 {
 8 public:
 9  void* m_pCSharpClass;
10  NativeCPP(void);
11  SayHello(void);
12 }
13 
14 NativeCPP::NativeCPP(void)
15 {
16   CSharpClass^ obj = new CSharpClass();
17   GCHandle^ handle = GCHandle.Alloc(obj)
18   m_pCSharpClass = GCHandle::ToIntPtr(handle).ToPointer();
19 }
20 
21 NativeCPP::SayHello(void)
22 {
23   GCHandle handle = GCHandle::FromIntPtr(m_pCSharpClass);
24   CSharpClass^ obj = dynamic_cast<CSharpClass^>(handle.Target);
25   obj.SayHello();
26 }

 

      不过这个时候有个问题:普通的C++类用new方式构造的实例可以用C++指针持有,在C++代码中没有调用delete的时候会一直存在于当前进程的地址空间中。这样在后面的使用中不会出现内存访问方面的问题,可是从C#到C++的过程就不是这的了。在C#中对象是由CLI自己管理的,当CLI发现一个对象不有直接或间接的引用存在的时候(也就是所谓的无根对象)就会被回收,这里的引用是指的CLI中的引用。另外C#的对象如果以引用的形式存在的话,那么在原始的C++里面中两个类之间传送这个对象将会变得不可能。因为除了托管C++外,其它的代码根本不能识别C#引用,只认识指针,因此我们必须把C#中的引用转成指针。可是如果通过简单的方式一旦转成指针后C#中的对象很可能会在没有引用的时候被GC回收了,造成C++代码再去访问的时候出内存导常。MS提供了一个名叫GCHandle的结构来解决这样的问题。通过这个结构可以构造一块内存区域代表这个引用对象,GC发现有这么一块内存区域存在的时候不管什么情况下都不会对该对象进行回收,用用类似的方法也可以从给定的内存指针上返回所指代的托管对象的引用。而这块内存本身不受托管代码控制,反回的也只是一个指针,因此在整个C++的代码生命周期里都不用再担心会引用到无效的C#对象了。

      这样的处理访问方式,保留了C#代码与C++代码各自的书写特别,真正做到了互不干挠,假如我再从C#换到VB.NET也是很easy的事情,这正是我想要的结果。到这里才发现原来C#与C++互相访问是如些的简单。其实现实就是这样,你知道的就很简单,不知道的就会觉得很难。