关于c#互操作调用C Dll多级指针一个想法。

在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;

当然,如果有大神知道其中的原因,欢迎在下方留言。

posted @ 2016-07-27 16:51  .NetDomainer  阅读(672)  评论(0编辑  收藏  举报