在c#调用c dll互操作中,c dll中的函数可能会有些多级指针作为参数或者返回值的情况,
本文的目的就是提供一种安全情况下,.net对于C DLL的调用想法,之所以叫想法,是因为这个方法
还是有些不完美的地方,哪里不完美,将在文章末尾为大家说明。
首先,关于.net(C#)如何调用c dll的基本方法,详见此文。
http://my.oschina.net/bubifengyun/blog/96252
我的环境为vs2015 community版本。
首先建立在你的解决方案添加两个控制台项目,它们分别为一个C#,C++控制台:
其中c++控制台项目创建时请选择控制台的Dll项目.而非默认的控制台项目。
在你的c++控制台项目中添加两个文件:MyDll.cpp,MyDll.h,内容如下:
MyDll.cpp
#include "stdafx.h"
#include "MyDLL.h"//貌似这两个头文件的顺序不能颠倒。我试了很多次,但是不能确定。
#include "stdlib.h"
int StateNum = 0;//全局状态量。
//MyStruct定义在MyDll.h中
/*以下为五种级别的结构体指针函数定义*/
MyStruct GetStruct(MyStruct st) {
st.MyVal = StateNum++;
return st;
}
MyStruct* GetStruct1(MyStruct *st) {
st->MyVal = StateNum++;
return st;
}
MyStruct** GetStruct2(MyStruct **st) {
(*st)->MyVal = StateNum++;
return st;
}
MyStruct*** GetStruct3(MyStruct ***st) {
(**st)->MyVal = StateNum++;
return st;
}
MyStruct**** GetStruct4(MyStruct ****st) {
(***st)->MyVal = StateNum++;
return st;
}
MyDll.h如下:
#ifndef LIB_H
#define LIB_H
typedef struct MyStruct {
int MyVal;
MyStruct *Next;
}MyStruct;
extern "C" _declspec(dllexport) MyStruct GetStruct(MyStruct st);
extern "C" _declspec(dllexport) MyStruct* GetStruct1(MyStruct *st);
extern "C" _declspec(dllexport) MyStruct** GetStruct2(MyStruct **st);
extern "C" _declspec(dllexport) MyStruct*** GetStruct3(MyStruct ***st);
extern "C" _declspec(dllexport) MyStruct**** GetStruct4(MyStruct ****st);
#endif
c#项目Program.cs编辑如下;
using System;
using System.Runtime.InteropServices;
namespace CDllInvoker {
[StructLayout(LayoutKind.Sequential,CharSet = CharSet.Ansi) ]
public struct MyStruct {
public int MyVal;
public IntPtr Next;//此处对应C中的结构体指针,本节不会用到,详情请见我的博客:
}
class Program {
//DLLInvoked.dll已在解决方案目录下,且其生成到目录属性被置为始终复制;
[DllImport("DLLInvoked.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public extern static MyStruct GetStruct(MyStruct st);//结构实体的传递及其接收
[DllImport("DLLInvoked.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public extern static IntPtr GetStruct1(IntPtr st); //一级指针的传递及其接收,之后以此类推
[DllImport("DLLInvoked.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public extern static IntPtr GetStruct2(IntPtr st);
[DllImport("DLLInvoked.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public extern static IntPtr GetStruct3(IntPtr st);
[DllImport("DLLInvoked.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public extern static IntPtr GetStruct4(IntPtr st);
//其中level为想要取的指针级别,若你对泛型还不了解,建议读者去看看C#的泛型知识;
static IntPtr GetPtrFromStructure<T>(T structure, short level = 1) where T:struct {
int sizeOfStructure = Marshal.SizeOf(typeof(T));
int sizeOfIntPtr = Marshal.SizeOf(typeof(IntPtr));
IntPtr ptr = Marshal.AllocHGlobal(sizeOfStructure);
Marshal.StructureToPtr(structure, ptr, true);
try {
for (int index = 1; index < level; index++) {
var innerPtr = Marshal.AllocHGlobal(sizeOfIntPtr);
Marshal.StructureToPtr(ptr, innerPtr, true);
Marshal.FreeHGlobal(innerPtr);
ptr = innerPtr;
}
return ptr;
}
catch (Exception ex) {
throw ex;
}
}
//其中level为传入指针的级别;
static T GetStructureFromPtr<T>(IntPtr ptr,short level = 1) where T:struct {
T entity;
IntPtr resPtr = ptr;
for(int index =1;index < level; index++) {
var innerPtr = Marshal.PtrToStructure<IntPtr>(resPtr);
resPtr = innerPtr;
}
entity = (T)Marshal.PtrToStructure(resPtr, typeof(T));
return entity;
}
static void Main(string[] args) {
int sizeOfmyStruct = Marshal.SizeOf(typeof(MyStruct));
MyStruct st = new MyStruct();
IntPtr ptr;
try {
Console.WriteLine("Invoking entity parameter with returning val...");
st = GetStruct(st);
Console.WriteLine(st.MyVal);
Console.WriteLine("Invoking level1 ptr with returning val...");
ptr = GetPtrFromStructure<MyStruct>(st, 1);
GetStruct1(ptr);
st = GetStructureFromPtr<MyStruct>(ptr, 1);
Console.WriteLine(st.MyVal);
Console.WriteLine("Invoking level2 ptr with returning val...");
ptr = GetPtrFromStructure<MyStruct>(st, 2);
GetStruct2(ptr);
st = GetStructureFromPtr<MyStruct>(ptr, 2);
Console.WriteLine(st.MyVal);
Console.WriteLine("Invoking level3 ptr with returning val...");
ptr = GetPtrFromStructure<MyStruct>(st, 3);
GetStruct3(ptr);
st = GetStructureFromPtr<MyStruct>(ptr, 3);
Console.WriteLine(st.MyVal);
Console.WriteLine("Invoking level4 ptr with returning val...");
ptr = GetPtrFromStructure<MyStruct>(st, 4);
GetStruct4(ptr);
st = GetStructureFromPtr<MyStruct>(ptr, 4);
Console.WriteLine(st.MyVal);
}
catch(AccessViolationException ex) {
Console.WriteLine(ex.Message);
//当level>2时,会有概率抛出此错误,至今还不清楚是什么原因
}
Console.Read();
}
}
}
其中,值得注意的是,当调用指针级别大于2时,有可能会出项错误,具体原因尚不清楚:
这里我推荐的方法是,若当你所需要的指针级别大于3时,请使用ref+低一级的指针,
以减少出现上述错误的可能。
比如若你想使用如上的MyStruct*** GetStruct(MyStruct ***st);
你可以使用如下的方法;
var ptr = GetPtrFromStructure<MyStruct>(st, 2);
GetStruct3(ref ptr);
st = GetStructureFromPtr<MyStruct>(ptr, 2);
Console.WriteLine(st.MyVal);
当然,在Program.cs中定义的GetStruct3方法也需要改变参数为ref IntPtr;
当然,如果有大神知道其中的原因,欢迎在下方留言。