在C#中使用C++代码


原文:http://xiali.spaces.live.com/blog/cns!473068a9be0aa907!391.entry?wa=wsignin1.0

手头上有不少的C++代码算法,不打算重写,但是希望利用一下。所以希望能够在C#中使用C++代码。
目前为止,我对CLR的理解就是,这个是用来帮助我们在C#中来利用C++的东西的一个手段。因为在CLR的方式下,我们可以调用unmanaged code(非托管代码)
好的,现在我们有以下的几种方案:
1. 在MSDN中提到,对于调用原有的WinAPI来说,DllImport是我们的一种选择。
 
using System.Runtime.InteropServices; // DllImport
public class Win32 {
  [DllImport("User32.Dll")]
  public static extern void SetWindowText(int h, String s);
}
    在C#中,你使用DllImpor来告诉编译器哪里是程序进入点。并且把你的外部函数绑到一个类里面。你能够给这个类任何名字。握着利用的是Win32,你甚至能够把这个类放在一个命名空间里面,要编译Win32API.cs, 这么打:
csc /t:library /out:Win32API.dll Win32API.cs
现在你有了一个 Win32API.dll,你能把它用到C#的工程里面:
using Win32API;
int hwnd = // get it...
String s = "I'm so cute."
Win32.SetWindowText(hwnd, s);

编译器知道去user32.dll里寻找SetWindowText(), 并且在调用之前自动的转换 string 到 LPTSTR(TCHAR*)
.NET 是如何实现的呢?每种C#类型都有一个默认的排列类型. 比如 string,它是 LPTSTR. 但是如果你试着用这个来做GetWindowText的参数,会怎么样呢?当这个string是一个输出的参数而不是一个输入的参数的时候,以上的方法就不行了,因为string 是不可变的。真的,可能你并没有注意到,但是当你对字符串进行操作的时候,总是生成一个新的string。要修改一个当前的string,你必须使用StringBuilder:
using System.Text; // for StringBuilder
public class Win32 {
  [DllImport("user32.dll")]
  public static extern int GetWindowText(int hwnd,
    StringBuilder buf, int nMaxCount);
}
默认的对应StringBuilder的marshaling type 也是LPTSTR, 但是现在GetWindowText函数能够真正的修改你的当前的string了.

int hwnd = // get it...
StringBuilder sb = new StringBuilder(256);
Win32.GetWindowText(hwnd, sb, sb.Capacity);

      所以对你的问题来说,解决方法就是使用StringBuilder.
      那些看来很不错哦,但是如果默认的marshaling type不是你想要的怎么办?比如,假设你想调用GetClassName? Windows® gurus 知道那和大多数API函数不同, GetClassName带LPSTR (char*)类型参数, 甚至是Unicode的构造.如果你传递string类型,the common language runtime (CLR) 会把它转换到CHARs—oops! 别怕,你可以使用MarshalAs来重载默认的:

[DllImport("user32.dll")]
public static extern int GetClassName(int hwnd,
  [MarshalAs(UnmanagedType.LPStr)] StringBuilder buf,
  int nMaxCount);现在当你调用GetClassName的时候, .NET 会当作ANSI char来传递你的字符串,而不是宽字符! 关于字符串就这么多了,现在来看看结构体和回调函数。 就像你可能猜测的一样, .NET 也有方法来解决它们。举个例子GetWindowRect.这个函数用指定窗口的坐标来填充RECT结构。
// in C/C++
RECT rc;
HWND hwnd = FindWindow("foo",NULL);
::GetWindowRect(hwnd, &rc);
      你如何从C#中来调用GetWindowRect?如何传递RECT?你把它当作是C#的结构体,使用另一个属性描述符 StructLayout.

[StructLayout(LayoutKind.Sequential)]
public struct RECT {
  public int left;
  public int top;
  public int right;
  public int bottom;
}当你已经定义了你的结构体的时候,你能够这样实现wrapper:[DllImport("user32.dll")]public static extern int GetWindowRect(int hwnd, ref RECT rc); 在这里使用ref前缀非常重要,这样CLR才能当参数传递你的RECT的时候,函数能够修改这个对象,而不是一个堆上的复制品。
当GetWindowRect已经被定义之后,你能像这样调用它:

RECT rc = new RECT();
int hwnd = // get it ...
Win32.GetWindowRect(hwnd, ref rc);
要注意的是,你必须在调用的时候也像声明的时候一样加上ref,这非常严格!默认的对应C#结构的marshaling type是 —LPStruct,所以这里不需要MarshalAs. 但是如果你用类RECT而不是结构体,你需要这样定义 wrapper :

// if RECT is a class, not struct
[DllImport("user32.dll")]
public static extern int
  GetWindowRect(int hwnd,
    [MarshalAs(UnmanagedType.LPStruct)] RECT rc);
     在C#里面,就和C++一样,有很多不同的方法来完成同一个目的。但是System.Drawing 已经有一个用来处理矩形的结构体了,我们何必再造轮子呢?
[DllImport("user32.dll")]
public static extern int GetWindowRect(int hwnd, ref Rectangle rc);
2.用托管C++包装现有的DLL,供C#调用  当函数的参数或返回值比较复杂,或函数比较多的时候,这种方法对与人来说,实在是一个折磨。常常这些接口和定义就要用掉几千行的代码,而且还不能保证是正确的。这些错误往往在运行时才能显现出来,甚至有些错误会引起内存泄漏,或其它更为隐蔽的错误。  在这种情况下,使用C++/Managed代码来包装,就成了最合理的选择。因为托管C++代码可以直接引用原有的头文件,直接调用非托管函数,而不需要声明。这样,既减少了工作量,又避免引入错误。缺点是,这种方法会增加一个DLL。要注意的是托管字符串和非托管字符串是有区别的,并需要转换(特别要注意的Unicode字符串和多字节字符串的转换)。 仍以MoveFile为例吧,这样比较简单:#include <windows.h>#include <vcclr.h>using namespace System;namespace wrapper{    public ref class ApiWrapper {    public:        bool static MoveFile(String ^ lpExistingFileName, String ^ lpNewFileName )        {            pin_ptr<const wchar_t> src = PtrToStringChars(lpExistingFileName);            pin_ptr<const wchar_t> dst = PtrToStringChars(lpNewFileName);            return ::MoveFile(src, dst);        }    };}  然后在C#中,引用上面代码生成的DLL文件,就可以直接调用了:wrapper.ApiWrapper.MoveFile(@"c:/debug.log", @"c:/debug.txt");  假如原有的代码是基于COM的,那么太好了,VisualStudio等IDE会自动生成一个用于包装的dll,供你调用。当然因特殊需要而手工编码的是另一回事。3.现有C++原代码,包装后供C#调用。
 C++的原代码,实际上可以直接编译成托管代码。MFC也好ATL也好……这样看起来在.NET中最强大的编程语言就是C++了:它不仅可以编写托管程序,甚至可以将标准C++的代码也编译成托管程序!其实VC++最强大的地方不止如此,它还在于能够编写混合了托管和非托管的代码的程序!!!这样最大的好处不仅可以将关键代码直接编译成非托管的代码,还可以避免被反编译。
  
  假设现有C++代码如下:

class UnmanagedClass {
public:
    LPCWSTR GetPropertyA() { return L"Hello!"; }
    void MethodB( LPCWSTR ) {}
};
  我们只要再增加一个包装类到工程文件中:
namespace wrapper
{
    public ref class ManagedClass {
    public:
        // Allocate the native object on the C++ Heap via a constructor
        ManagedClass() : m_Impl( new UnmanagedClass ) {}

        // Deallocate the native object on a destructor
        ~ManagedClass() {
            delete m_Impl;
        }

    protected:
        // Deallocate the native object on the finalizer just in case no destructor is called
        !ManagedClass() {
            delete m_Impl;
        }

    public:
        property String ^  get_PropertyA {
            String ^ get() {
                return gcnew String( m_Impl->GetPropertyA());
            }
        }

        void MethodB( String ^ theString ) {
            pin_ptr<const WCHAR> str = PtrToStringChars(theString);
            m_Impl->MethodB(str);
        }

    private:
        UnmanagedClass * m_Impl;
    };
}

  然后,改变编译选项为“使用公共语言扩展 /clr”就可以了。这样,我们把代码编译成DLL文件就可以供.NET其它语言调用了。
  最后,C#中可以象如下的代码一样调用C++类了:
ManagedClass mc = new ManagedClass();
mc.MethoB("Hello");
string s = mc.get_PropertyA;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值