使用DllImport 与 c++链接库 交互

最近公司做的项目需要c++开发人员编写的算法与.NET交互,这个c# 与 c++编写的链接库打交道那叫一个累!

最恶心的是 **int[] 指针的指针..后来发现其实操作最麻烦的也就是类型的对应,

这个 **int[]  最后竟然使用 IntPtr[] 接收成功了。有意思。单个IntPtr接收不了。。(主要是C++不太了解)

--------------------------进入主题-------------------------

基本语法:(必须是静态的,必须加extern修饰符,必须静态导入DLL链接库)

[DllImport("***********.dll")]//你要导入的库函数
public static extern void GetData(IntPtr[] itp,ref int len,ref double osqe);

//这样你就可以使用这个库函数里面的方法了
//基本使用语法扩展

可选的 DllImportAttribute 属性

除了指出宿主 DLL 外,DllImportAttribute 还包含了一些可选属性,其中四个特别有趣:EntryPoint、CharSet、SetLastError 和 CallingConvention。

----------------1---------------------

EntryPoint 在不希望外部托管方法具有与 DLL 导出相同的名称的情况下,可以设置该属性来指示导出的 DLL 函数的入口点名称。

当您定义两个调用相同非托管函数的外部方法时,这特别有用。另外,在 Windows 中还可以通过它们的序号值绑定到导出的 DLL 函数。

如果您需要这样做,则诸如“#1”或“#129”的 EntryPoint 值指示 DLL 中非托管函数的序号值而不是函数名。

----------------2---------------------

CharSet 对于字符集,并非所有版本的 Windows 都是同样创建的。Windows 9x 系列产品缺少重要的 Unicode 支持,

而 Windows NT 和 Windows CE 系列则一开始就使用 Unicode。在这些操作系统上运行的 CLR 将Unicode 用于 String 和 Char 数据的内部表示。

但也不必担心 — 当调用 Windows 9x API 函数时,CLR 会自动进行必要的转换,将其从 Unicode转换为 ANSI。

如果 DLL 函数不以任何方式处理文本,则可以忽略 DllImportAttribute 的 CharSet 属性。然而,当 Char 或 String 数据是等式的一部分时,

应该将 CharSet 属性设置为 CharSet.Auto。这样可以使 CLR 根据宿主 OS 使用适当的字符集。如果没有显式地设置 CharSet 属性,则其默认值为 CharSet.Ansi。

这个默认值是有缺点的,因为对于在 Windows 2000、Windows XP 和 Windows NT® 上进行的 interop 调用,它会消极地影响文本参数封送处理的性能。

应该显式地选择 CharSet.Ansi 或 CharSet.Unicode 的 CharSet 值而不是使用 CharSet.Auto 的唯一情况是:您显式地指定了一个导出函数,

而该函数特定于这两种 Win32 OS 中的某一种。ReadDirectoryChangesW API 函数就是这样的一个例子,它只存在于基于 Windows NT 的操作系统中,

并且只支持 Unicode;在这种情况下,您应该显式地使用 CharSet.Unicode。

有时,Windows API 是否有字符集关系并不明显。一种决不会有错的确认方法是在 Platform SDK 中检查该函数的 C 语言头文件。

(如果您无法肯定要看哪个头文件,则可以查看 Platform SDK 文档中列出的每个 API 函数的头文件。)如果您发现该 API 函数确实定义为一个映射到以 A 或 W 结尾的函数名的宏,

则字符集与您尝试调用的函数有关系。Windows API 函数的一个例子是在 WinUser.h 中声明的 GetMessage API,您也许会惊讶地发现它有 A 和 W 两种版本。

----------------3---------------------

SetLastError 错误处理非常重要,但在编程时经常被遗忘。当您进行 P/Invoke 调用时,

也会面临其他的挑战 — 处理托管代码中 Windows API 错误处理和异常之间的区别。我可以给您一点建议。

如果您正在使用 P/Invoke 调用 Windows API 函数,而对于该函数,您使用 GetLastError 来查找扩展的错误信息,

则应该在外部方法的 DllImportAttribute 中将 SetLastError 属性设置为 true。这适用于大多数外部方法。

这会导致 CLR 在每次调用外部方法之后缓存由 API 函数设置的错误。然后,在包装方法中,

可以通过调用类库的 System.Runtime.InteropServices.Marshal 类型中定义的 Marshal.GetLastWin32Error 方法来获取缓存的错误值。

我的建议是检查这些期望来自 API 函数的错误值,并为这些值引发一个可感知的异常。对于其他所有失败情况(包括根本就没意料到的失败情况),

则引发在 System.ComponentModel 命名空间中定义的 Win32Exception,并将 Marshal.GetLastWin32Error 返回的值传递给它。

----------------4---------------------

CallingConvention 我将在此介绍的最后也可能是最不重要的一个 DllImportAttribute 属性是 CallingConvention。通过此属性,

可以给 CLR 指示应该将哪种函数调用约定用于堆栈中的参数。CallingConvention.Winapi 的默认值是最好的选择,它在大多数情况下都可行。

然而,如果该调用不起作用,则可以检查 Platform SDK 中的声明头文件,看看您调用的 API 函数是否是一个不符合调用约定标准的异常 API。

通常,本机函数(例如 Windows API 函数或 C- 运行时 DLL 函数)的调用约定描述了如何将参数推入线程堆栈或从线程堆栈中清除。

大多数 Windows API 函数都是首先将函数的最后一个参数推入堆栈,然后由被调用的函数负责清理该堆栈。相反,

许多 C-运行时 DLL 函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈,将堆栈清理工作交给调用者。

幸运的是,要让 P/Invoke 调用工作只需要让外围设备理解调用约定即可。通常,从默认值 CallingConvention.Winapi 开始是最好的选择。

然后,在 C 运行时 DLL 函数和少数函数中,可能需要将约定更改为 CallingConvention.Cdecl。

//C#与c++ 基本类型对应:

c++ -------->对应-------->c#

# BOOL, BOOLEAN ----------- Boolean or Int32 

# BSTR ------------ String 

# BYTE -------------- Byte 

# CHAR ------------ Char 

# DOUBLEc ------------- Double 

# DWORD ---------------Int32 or UInt32 

# FLOAT ----------------- Single 

# HANDLE (其他指针类型) ------------ IntPtr, UintPtr or HandleRef 

# HRESULT -------------- Int32 or UInt32 

# INT -------------- Int32 

# LANGID --------------- Int16 or UInt16 

# LCID --------------- Int32 or UInt32 

# LONG ------------------- Int64 or Int32

# LPARAM ----------------- IntPtr, UintPtr or Object 

# LPCSTR ---------------- String 

# LPCTSTR ---------------- String 

# LPCWSTR ----------------- String 

# LPSTR ------------------- String or StringBuilder* 

# LPTSTR --------------- String or StringBuilder 

# LPWSTR ---------------- String or StringBuilder 

# LPVOID ---------------- IntPtr, UintPtr or Object 

# LRESULT ----------------- IntPtr 

# SAFEARRAY ---------------- .NET array type (数组类型)

# SHORT ------------------- Int16 

# TCHAR -------------------- Char 

# UCHAR ------------------- SByte 

# UINT ----------------- Int32 or UInt32 

# ULONG ----------------- Int32 or UInt32 

# VARIANT ---------------- Object 

# VARIANT_BOOL ------------- Boolean 

# WCHAR ------------------- Char 

# WORD --------------------- Int16 or UInt16 

# WPARAM ------------------- IntPtr, UintPtr or Object 



# 另注意: 在进行string转换时,需要加入前缀[MarshalAs(UnmanagedType.LPStr)]
lpdword ------------- ref int

基本原则有如下凢点:

1、各种句柄类的(H开头),我认为一律湜System.IntPtr ;如果只知道是数组头指针可以采用 IntPtr[] 接收.在使用Marshal类操作内存.

2、LP和P,我不太懂(对C 不忲太解),对于LP和P开头的函数,如果是和STR有关的,一律写为System.String,像PLCID这样指向什么东西滴,写为System.UInt32(因为指向另一个地址,那就是指针),int之类的数值型,那我就写为int[].

3.如果是按引用传递,也就是按地址传递,请使用 ref or out,

4.如果是比较复杂的结构体指针,请构建出相对应c# 结构体(struct)来.如何构建可以参照下面的,只是一个示例

 //申明结构在内存中布局   
 [StructLayout(LayoutKind.Sequential)]
  public struct information
  {
  //设置字符串封送方式
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string szCSDVersion;
  public ushort wServicePackMajor;
  public byte wProductType;
  }


-------c#操作C++地址常用类--------

Marshal (class)有许多操作地址的静态方法给我们开发者使用,

如:Marshal.Copy(......)这个方法非常实用,特别是操作只有指针头文件的数组.具体操作自行参考资料.

posted @ 2011-04-18 20:48  JasNature  阅读(3724)  评论(1编辑  收藏  举报
我要赞个
我要评论
我要收藏