net平台上,调用dll文件有2种含义1、调用托管dll,即你使用。net平台开发的dll,属于托管代码2、调用非托管dll,即传统的dll,一般是C++,VB,DELPHI等等开发出来的,属于非托管代码。从你的意思中看出来你现在是调用托管的dll,方法是 “在解决方案管理器” - “解决方案”(或项目) 中的任意地方, 右键“添加引用”,“浏览”,选择你需要调用的dll文件,确定即可,该dll会自动复制到bin目录,打包时也会自动复制到你发布的地方。添加完了引用,现在如何调用呢?如果有命名空间则引入命名空间,比如你的y。dll里面,是a命名空间,有一个b类,然后有一个无参数静态方法c那么调用方法就是a.b.c(),跟你普通的使用类是一样的然后是非托管dll需要添加dll的名称,以及方法,也就是你所用到的dll的每个方法都需要添加一次,[DllImport("msvcrt.dll")]    public static extern int puts(string c);

*******************************************************************************************

在最近的项目中,牵涉到项目源代码保密问题,由于代码是C#写的,容易被反编译,因此决定抽取核心算法部分使用C++编写,C++到目前为止好像还不能被很好的反编译,当然如果你是反汇编高手的话,也许还是有可能反编译。这样一来,就涉及C#托管代码与C++非托管代码互相调用,于是调查了一些资料,顺便与大家分享一下:源代码下载

一. C# 中静态调用C++动态链接


   1. 建立VC工程CppDemo,建立的时候选择Win32 Console(dll),选择Dll。

   2. 在DllDemo.cpp文件中添加这些代码。

Code
extern"C" __declspec(dllexport) int Add(int a,int b)
{

return a+b;
}

   3. 编译工程。

   4. 建立新的C#工程,选择Console应用程序,建立测试程序InteropDemo
   5. 在Program.cs中添加引用:using System.Runtime.InteropServices;

   6. 在pulic class Program添加如下代码:


Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace InteropDemo
{
class Program
   {
       [DllImport("CppDemo.dll", EntryPoint = "Add", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]
publicstaticexternint Add(int a, int b); //DllImport请参照MSDN

staticvoid Main(string[] args)
       {
           Console.WriteLine(Add(1, 2));
           Console.Read();
       }
   }
}

  好了,现在您可以测试Add程序了,是不是可以在C# 中调用C++动态链接了,当然这是静态调用,需要将CppDemo编译生成的Dll放在DllDemo程序的Bin目录下

二. C# 中动态调用C++动态链接

在第一节中,讲了静态调用C++动态链接,由于Dll路径的限制,使用的不是很方便,C#中我们经常通过配置动态的调用托管Dll,例如常用的一些设计模式:Abstract Factory, Provider, Strategy模式等等,那么是不是也可以这样动态调用C++动态链接呢?只要您还记得在C++中,通过LoadLibrary, GetProcess, FreeLibrary这几个函数是可以动态调用动态链接的(它们包含在kernel32.dll中),那么问题迎刃而解了,下面我们一步一步实验

   1.   将kernel32中的几个方法封装成本地调用类NativeMethod

Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace InteropDemo
{
publicstaticclass NativeMethod
   {
       [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
publicstaticexternint LoadLibrary(
           [MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);

       [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
publicstaticextern IntPtr GetProcAddress(int hModule,
           [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

       [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
publicstaticexternbool FreeLibrary(int hModule);
   }
}


   2. 使用NativeMethod类动态读取C++Dll,获得函数指针,并且将指针封装成C#中的委托。原因很简单,C#中已经不能使用指针了,如下          
           int hModule = NativeMethod.LoadLibrary(@"c:"CppDemo.dll");

           IntPtr intPtr = NativeMethod.GetProcAddress(hModule, "Add");

详细请参见代码

Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace InteropDemo
{
class Program
   {
//[DllImport("CppDemo.dll", EntryPoint = "Add", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]
//public static extern int Add(int a, int b); //DllImport请参照MSDN


staticvoid Main(string[] args)
       {
//1. 动态加载C++ Dll
int hModule = NativeMethod.LoadLibrary(@"c:\CppDemo.dll");
if (hModule == 0) return;

//2. 读取函数指针
           IntPtr intPtr = NativeMethod.GetProcAddress(hModule, "Add");

//3. 将函数指针封装成委托
           Add addFunction = (Add)Marshal.GetDelegateForFunctionPointer(intPtr, typeof(Add));

//4. 测试
           Console.WriteLine(addFunction(1, 2));
           Console.Read();
       }

///<summary>
/// 函数指针
///</summary>
///<param name="a"></param>
///<param name="b"></param>
///<returns></returns>
delegateint Add(int a, int b);

   }
}

*****************************************************************************************

分别介绍 DllImportAttribute属性、extern关键字 、IntPtr类型 这三个方面,向大家介绍如何应用C#调用非托管DLL函数。


C# 如何使用 DllImport Attribute(属性) 标识 DLL 和函数

System.Runtime.InteropServices.DllImportAttribute

从托管代码中访问非托管 DLL 函数之前,需要知道该函数的名称以及该 DLL 的名称,然后为 DLL 的非托管函数 编写 托管定义。

它将用到 static 和 extern 修饰符,此类型的公共静态成员对于多线程操作是安全的。

DllImport 属性提供非托管 DLL 函数的调用信息。


示例1  简单DllImportAttribute 应用

using System.Runtime.InteropServices;
[DllImport("user32.dll")]    
publicstaticexternint MessageBox(int hWnd, String text, String caption, uint type);

示例2  如何将 DllImportAttribute 应用于方法。

using System.Runtime.InteropServices;
[DllImport(  "KERNEL32.DLL",
            EntryPoint="MoveFileW",
            SetLastError=true,
            CharSet=CharSet.Unicode,
            ExactSpelling=true,
            CallingConvention=CallingConvention.StdCall
         )
]
publicstaticexternbool MoveFile(String src, String dst);

参数说明:

EntryPoint         指定要调用的 DLL 入口点。

SetLastError       判断在执行该方法时是否出错(使用 Marshal.GetLastWin32Error API 函数来确定)。
                  C#中默认值为 false。

CharSet            控制名称及函数中字符串参数的编码方式。默认值为 CharSet.Ansi。

ExactSpelling      是否修改入口点以对应不同的字符编码方式。

CallingConvention  指定用于传递方法参数的调用约定。默认值为 WinAPI。
                  该值对应于基于32位Intel平台的 __stdcall。

BestFitMapping     是否启用最佳映射功能,默认为 true。
                  最佳映射功能提供在没有匹配项时,自动提供匹配的字符。
                  无法映射的字符通常转换为默认的“?”。

PreserveSig        托管方法签名是否转换成返回 HRESULT,默认值为 true(不应转换签名)。
                  并且返回值有一个附加的 [out, retval] 参数的非托管签名。

ThrowOnUnmappableChar     控制对转换为 ANSI '?' 字符的不可映射的 Unicode 字符引发异常。


C# 关键字 extern 的使用

public static extern int MyMethod(int x);

外部修饰符 extern 用于指示外部实现方法,常与 DllImport 属性一起使用(DllImport 属性提供非托管 DLL 函数的调用信息)。

若将 abstract 和 extern 修饰符一起使用来修改同一成员是错误的。extern 将方法在 C# 代码的外部实现,而 abstract 意味着在此类中未提供此方法的实现。

因为外部方法声明不提供具体实现,所以没有方法体;
此方法声明只是以一个分号结束,并且在签名后没有大括号{ }。

示例3  接收用户输入的字符串并显示在消息框中

程序从 User32.dll 库导入的 MessageBox 方法。

using System;
using System.Runtime.InteropServices;
class MyClass
{
  [DllImport("User32.dll")]
publicstaticexternint MessageBox(int h, string m, string c, int type);

publicstaticint Main()
  {
string myString;
     Console.Write("Enter your message: ");
     myString = Console.ReadLine();
return MessageBox(0, myString, "My Message Box", 0);
  }
}

运行结果: 输入"Hello"文本后,屏幕上将弹出一个包含该文本的消息框。
Enter your message: Hello

示例4  调用DLL进行计算

该示例使用两个文件 CM.cs 和 Cmdll.c 来说明 extern。
C 文件是从 C# 程序中调用的外部 DLL。

使用 Visual C++ 命令行将 Cmdll.c 编译为 DLL:
cl /LD /MD Cmdll.c

文件:Cmdll.c

// cmdll.c
// compile with: /LD /MD
int __declspec(dllexport) MyMethod(int i)
{
return i*10;
}


使用命令行编译 CM.cs:
csc CM.cs
这将创建可执行文件 CM.exe。


文件:CM.cs

// cm.cs
using System;
using System.Runtime.InteropServices;
publicclass MyClass
{
  [DllImport("Cmdll.dll")]
publicstaticexternint MyMethod(int x);

publicstaticvoid Main()
  {
     Console.WriteLine("MyMethod() returns {0}.", MyMethod(5));
  }
}

运行此程序,MyMethod 将值 5 传递到 DLL 文件,该文件将此值乘以 10 返回。
运行结果:MyMethod() returns 50.


IntPtr 类型的说明

对于平台调用,应让参数为 IntPtr 类型,而不是 String 类型。使用 System.Runtime.InteropServices.Marshal 类所提供的方法,可将类型手动转换为字符串并手动将其释放。

IntPtr 类型被设计成整数,其大小适用于特定平台。
                 在 32 位硬件和操作系统中将是 32 位;
                 在 64 位硬件和操作系统中将是 64 位。

IntPtr 类型由支持指针的语言使用,并作为在支持与不支持指针的语言间引用数据的一种通用方式。它也可用于保持句柄。例如,IntPtr 的实例广泛地用在 System.IO.FileStream 类中来保持文件句柄。

IntPtr 类型符合 CLS,而 UIntPtr 类型却不符合。只有 IntPtr 类型可用在公共语言运行库中。此类型实现 ISerializable 接口。

*****************************************************************************************