C#总结(五)调用C++动态库(类型对照)

函数调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。
在dllimport中加入CallingConvention参数就行了,

[DllImport(PCAP_DLL, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]


要注意C++与NET中数据类型的对应:

//c++:char * —- c#:string //传入参数
//c++:char * —- c#:StringBuilder//传出参数
//c++:char *变量名 —- c#:ref string 变量名
//c++:char *输入变量名 —- c#:string 输入变量名
//c++:char *输出变量名 —- c#:[MarshalAs(UnmanagedType.LPStr)] StringBuilder 输出变量名
//c++:SHORT(short) —- c#:System.Int16
//c++:LONG(long) —- c#:System.Int32

转载收集:

//C#调用C++的DLL搜集整理的所有数据类型转换方式,可能会有重复或者多种方案,自己多测试
//c++:HANDLE(void *) —- c#:System.IntPtr
//c++:Byte(unsigned char) —- c#:System.Byte
//c++:SHORT(short) —- c#:System.Int16
//c++:WORD(unsigned short) —- c#:System.UInt16
//c++:INT(int) —- c#:System.Int16
//c++:INT(int) —- c#:System.Int32
//c++:UINT(unsigned int) —- c#:System.UInt16
//c++:UINT(unsigned int) —- c#:System.UInt32
//c++:LONG(long) —- c#:System.Int32
//c++:ULONG(unsigned long) —- c#:System.UInt32
//c++:DWORD(unsigned long) —- c#:System.UInt32
//c++:DECIMAL —- c#:System.Decimal
//c++:BOOL(long) —- c#:System.Boolean
//c++:CHAR(char) —- c#:System.Char
//c++:LPSTR(char *) —- c#:System.String
//c++:LPWSTR(wchar_t *) —- c#:System.String
//c++:LPCSTR(const char *) —- c#:System.String
//c++:LPCWSTR(const wchar_t *) —- c#:System.String
//c++:PCAHR(char *) —- c#:System.String
//c++:BSTR —- c#:System.String
//c++:FLOAT(float) —- c#:System.Single
//c++:DOUBLE(double) —- c#:System.Double
//c++:VARIANT —- c#:System.Object
//c++:PBYTE(byte *) —- c#:System.Byte[]

//c++:BSTR —- c#:StringBuilder
//c++:LPCTSTR —- c#:StringBuilder
//c++:LPCTSTR —- c#:string
//c++:LPTSTR —- c#:[MarshalAs(UnmanagedType.LPTStr)] string
//c++:LPTSTR 输出变量名 —- c#:StringBuilder 输出变量名
//c++:LPCWSTR —- c#:IntPtr
//c++:BOOL —- c#:bool
//c++:HMODULE —- c#:IntPtr
//c++:HINSTANCE —- c#:IntPtr
//c++:结构体 —- c#:public struct 结构体{};
//c++:结构体 **变量名 —- c#:out 变量名 //C#中提前申明一个结构体实例化后的变量名
//c++:结构体 &变量名 —- c#:ref 结构体 变量名

//c++:WORD —- c#:ushort
//c++:DWORD —- c#:uint
//c++:DWORD —- c#:int

//c++:UCHAR —- c#:int
//c++:UCHAR —- c#:byte
//c++:UCHAR* —- c#:string
//c++:UCHAR* —- c#:IntPtr

//c++:GUID —- c#:Guid
//c++:Handle —- c#:IntPtr
//c++:HWND —- c#:IntPtr
//c++:DWORD —- c#:int
//c++:COLORREF —- c#:uint

//c++:unsigned char —- c#:byte
//c++:unsigned char * —- c#:ref byte
//c++:unsigned char * —- c#:[MarshalAs(UnmanagedType.LPArray)] byte[]
//c++:unsigned char * —- c#:[MarshalAs(UnmanagedType.LPArray)] Intptr

//c++:unsigned char & —- c#:ref byte
//c++:unsigned char 变量名 —- c#:byte 变量名
//c++:unsigned short 变量名 —- c#:ushort 变量名
//c++:unsigned int 变量名 —- c#:uint 变量名
//c++:unsigned long 变量名 —- c#:ulong 变量名

//c++:char 变量名 —- c#:byte 变量名 //C++中一个字符用一个字节表示,C#中一个字符用两个字节表示
//c++:char 数组名[数组大小] —- c#:MarshalAs(UnmanagedType.ByValTStr, SizeConst = 数组大小)] public string 数组名; ushort

//c++:char * —- c#:string //传入参数
//c++:char * —- c#:StringBuilder//传出参数
//c++:char *变量名 —- c#:ref string 变量名
//c++:char *输入变量名 —- c#:string 输入变量名
//c++:char *输出变量名 —- c#:[MarshalAs(UnmanagedType.LPStr)] StringBuilder 输出变量名

//c++:char ** —- c#:string
//c++:char **变量名 —- c#:ref string 变量名
//c++:const char * —- c#:string
//c++:char[] —- c#:string
//c++:char 变量名[数组大小] —- c#:[MarshalAs(UnmanagedType.ByValTStr,SizeConst=数组大小)] public string 变量名;

//c++:struct 结构体名 *变量名 —- c#:ref 结构体名 变量名
//c++:委托 变量名 —- c#:委托 变量名

//c++:int —- c#:int
//c++:int —- c#:ref int
//c++:int & —- c#:ref int
//c++:int * —- c#:ref int //C#中调用前需定义int 变量名 = 0;

//c++:*int —- c#:IntPtr
//c++:int32 PIPTR * —- c#:int32[]
//c++:float PIPTR * —- c#:float[]

//c++:double** 数组名 —- c#:ref double 数组名
//c++:double*[] 数组名 —- c#:ref double 数组名
//c++:long —- c#:int
//c++:ulong —- c#:int

//c++:UINT8 * —- c#:ref byte //C#中调用前需定义byte 变量名 = new byte();

//c++:handle —- c#:IntPtr
//c++:hwnd —- c#:IntPtr

//c++:void * —- c#:IntPtr
//c++:void * user_obj_param —- c#:IntPtr user_obj_param
//c++:void * 对象名称 —- c#:([MarshalAs(UnmanagedType.AsAny)]Object 对象名称

//c++:char, INT8, SBYTE, CHAR —- c#:System.SByte
//c++:short, short int, INT16, SHORT —- c#:System.Int16
//c++:int, long, long int, INT32, LONG32, BOOL , INT —- c#:System.Int32
//c++:__int64, INT64, LONGLONG —- c#:System.Int64
//c++:unsigned char, UINT8, UCHAR , BYTE —- c#:System.Byte
//c++:unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t —- c#:System.UInt16
//c++:unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT —- c#:System.UInt32
//c++:unsigned __int64, UINT64, DWORDLONG, ULONGLONG —- c#:System.UInt64
//c++:float, FLOAT —- c#:System.Single
//c++:double, long double, DOUBLE —- c#:System.Double

//Win32 Types —- CLR Type

//Struct需要在C#里重新定义一个Struct
//CallBack回调函数需要封装在一个委托里,delegate static extern int FunCallBack(string str);

//unsigned char** ppImage替换成IntPtr ppImage
//int& nWidth替换成ref int nWidth
//int*, int&, 则都可用 ref int 对应
//双针指类型参数,可以用 ref IntPtr
//函数指针使用c++: typedef double (*fun_type1)(double); 对应 c#:public delegate double fun_type1(double);
//char* 的操作c++: char*; 对应 c#:StringBuilder;
//c#中使用指针:在需要使用指针的地方 加 unsafe

//unsigned char对应public byte
/*
* typedef void (*CALLBACKFUN1W)(wchar_t*, void* pArg);
* typedef void (*CALLBACKFUN1A)(char*, void* pArg);
* bool BIOPRINT_SENSOR_API dllFun1(CALLBACKFUN1 pCallbackFun1, void* pArg);
* 调用方式为
* [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
* public delegate void CallbackFunc1([MarshalAs(UnmanagedType.LPWStr)] StringBuilder strName, IntPtr pArg);
*
*
*/

C#总结(四)调用C++动态库

由于公司很多底层的SDK,都是C++开发,上层的应用软件却是C# Winform程序。在实际工作的过程中,就经常碰到了C# 程序调用C++ 动态库的问题。最近一直在和C++ 打交道,C# 怎么调用C++ 类库函数。也遇到了一些问题,所以就来总结总结C#程序调用C++动态库时的各种坑。

 

1. 可能遇到的问题:

C#在调用动态库的过程中我也遇到了以下一些问题:

1、C++中有指针,C#中需要使用指针吗?

由于C++中的动态库中有指针参数,因此我也是用.NET的不安全代码,使用了C#的指针,但是也还是出现了一些问题,如在C#中传入的参数是一个二维数组时就出现了问题,最后只能改C++函数传入参数的参数类型。

2、C#和C++中的类型如何转换呢?

虽然C#和C++比较类似,但是其给我们的参数类型我们要与C#的参数类型一一对应起来,具体看后续说明。

3、C++函数中的CallingConventionCharSet 怎么设置?

调用C++函数之前一定要先确认,否则可能出现函数调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配的问题。函数的CallingConvention和CharSet,可以查看动态库对应的 .h头文件。

4、如何反编译C++的dll的名称,端口?

可以通过Dependency Walker工具进行反编译查看别人写的动态库的信息

5、指针函数如何传参?

对于函数需要的指针函数,C# 调用时,可以定义委托来传入参数。

6、需要注意C++ dll 编译的平台是x86还是x64,是多字节的还是双字节的(Unicode)。

 

  2. 通过Dependency Walke查看dll的名称,端口

  下载Dependency 后将对应的C++ dll文件加载进去,就尅看到动态库的对应的信息,同时也可以通过.h 头文件查看。

 

 3. 如何调用

c#调用c++动态库一般我们这样写

    [DllImport(SDK, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    public static extern int IKSDK_Release();
 1. DllImport的第一个参数SDK是动态库dll的路径,此dll放在程序运行的根目录或者c:windows/sytem32下,建议在程序根目录创建一个子目录来放置相应的C++ 动态库文件,方便以后更新。

2. CallingConvention 参数是c#调用c++的方式 是个枚举 msdn解释如下:

Cdecl 调用方清理堆栈。这使您能够调用具有 varargs 的函数(如 Printf),使之可用于接受可变数目的参数的方法。
FastCal 不支持此调用约定。
StdCall 被调用方清理堆栈。这是使用平台 invoke 调用非托管函数的默认约定。
ThisCall 第一个参数是 this 指针,它存储在寄存器 ECX 中。其他参数被推送到堆栈上。此调用约定用于对从非托管 DLL 导出的类调用方法。
Winapi 此成员实际上不是调用约定,而是使用了默认平台调用约定。例如,在 Windows 上默认为 StdCall,在 Windows CE.NET 上默认为 Cdecl

3. CharSet参数是控制名称重整以及将字符串参数封送到函数中的方式。 默认值为 CharSet.Ansi。

4. entrypoint参数用于标识函数在 DLL 中的位置。在托管对象中,目标函数的原名或序号入口点将标识跨越交互操作边界的函数。此外,您可以将入口点映射到一个不同的名称,这实际上是将函数重命名。一般默认不设置此参数。

5. 其他参数,请查看MSDN对于 DllImportAttribute 的说明。

 

4. 其他说明

C# 调用C++ 动态库,还有一个特别麻烦的问题就是参数对于的问题。后续会结合网上的资料总结一份详细的对照表。

 

C#总结(二)事件Event 介绍总结

  最近在总结一些基础的东西,主要是学起来很难懂,但是在日常又有可能会经常用到的东西。前面介绍了 C# 的 AutoResetEvent的使用介绍, 这次介绍事件(event)。

  事件(event),对于初学者来说,确实比较神秘,难懂。但是在日常编程过程中却经常遇到。事件使用得当,会让你的代码更加整洁,也能少些很多代码。

 

  一、Event事件,是一种封装过的委托。

  它拥有以下三要素:

    1. 事件发行者:达到某些条件时激发事件的对象

    2. 事件订阅者:订阅事件并对事件发生时进行处理的对象

    3. 定义发行者和订阅者关系,一个发行者可能会有多个订阅者。

  事件发行者和事件订阅者通过委托(delegate) 来实现发送方和接收方的事件触发。

  它拥有哪些好处:

    在以往我们编写订阅这类程序中,往往采用等待机制,为了等待某件事情的发生,需要不断地检测事情什么时候发生,而通过事件(event),可以大大简化了这种过程:

    1. 使用事件,可以很方便地确定程序执行顺序。

    2. 当事件驱动程序等待事件时,它不占用很多资源。事件驱动程序与过程式程序最大的不同就在于,程序不再不停地检查输入设备,而是呆着不动,等待消息的到来,每个输入的消息会被排进队列,等待程序处理它。如果没有消息在等待,则程序会把控制交回给操作系统,以运行其他程序。

    3. 事件简化了编程。事件订阅者只是简单地将消息传送给事件发行者,由发行者的事件驱动程序确定事件的处理方法。不必知道程序的内部订阅触发机制,只是需要知道如何传递消息即可。

  二、事件和委托的区别

    1.委托允许直接通过委托去访问相应的处理函数,而事件只能通过公布的回调函数去调用,

    2.事件只能通过“+=”,“-=”方式注册和取消订户处理函数,而委托除此之外还可以使用“=”直接赋值处理函数。

 

  三、事件的声明(Event)

    在类的内部声明事件,首先必须声明该事件的委托类型。例如:

    public delegate void NumManipulationHandler(NumEventArgs e);

    然后,声明事件本身,使用 event 关键字:

    // 基于上面的委托定义事件
    public event NumManipulationHandler ChangeNum;

    上面的代码定义了一个名为 NumManipulationHandler 的委托和一个名为 ChangeNum 的事件,该事件是在某个值生成的时候会调用委托事件。

 

  四、实例

    public class Program
    {
        public static void Main()
        {

            NumEvent even = new NumEvent(0);
            even.ChangeNum += EventAction.Action;

            even.SetValue(7);
            even.SetValue(11);

            System.Console.ReadKey();
        }
    }

    public class NumEvent
    {
        private int value;

        public delegate void NumManipulationHandler(NumEventArgs e);

        public event NumManipulationHandler ChangeNum;

        public virtual void OnChangeNum(NumEventArgs e)
        {
            ChangeNum?.Invoke(e);
        }

        public NumEvent(int n)
        {
            SetValue(n);
        }

        public void SetValue(int n)
        {
            if (value != n)
            {
                NumEventArgs e = new NumEventArgs(n);
                value = n;
                OnChangeNum(e);
            }
        }
    }

    public class EventAction
    {
        public static void Action(NumEventArgs e)
        {
            System.Console.WriteLine("value : " + e.value);
        }
    }

    public class NumEventArgs : EventArgs
    {
        public int value;
        public NumEventArgs(int _value)
        {
            this.value = _value;
        }
    }

 

C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步)

  前几天碰到一个线程的顺序执行的问题,就是一个异步线程往A接口发送一个数据请求。另外一个异步线程往B接口发送一个数据请求,当A和B都执行成功了,再往C接口发送一个请求。说真的,一直做BS项目,对线程了解,还真不多。就知道AutoResetEvent这个东西和线程有关,用于处理线程切换之类,于是决定用AutoResetEvent来处理上面的问题。
 
  于是网上查找相关资料:
 
  原来,AutoResetEvent在.Net多线程编程中,经常用到。当某个线程调用WaitOne方法后,信号处于发送状态,该线程会得到信号, 程序就会继续向下执行,否则就等待。而且 AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,其他调用WaitOne的线程只有继续等待.也就是说,AutoResetEvent一次只唤醒一个线程,其他线程还是堵塞。
 
  简介
   AutoResetEvent(bool initialState):构造函数,用一个指示是否将初始状态设置为终止的布尔值初始化该类的新实例。
        false:无信号,子线程的WaitOne方法不会被自动调用
        true:有信号,子线程的WaitOne方法会被自动调用
   Reset ():将事件状态设置为非终止状态,导致线程阻止;如果该操作成功,则返回true;否则,返回false。
   Set ():将事件状态设置为终止状态,允许一个或多个等待线程继续;如果该操作成功,则返回true;否则,返回false。
   WaitOne(): 阻止当前线程,直到收到信号。
   WaitOne(TimeSpan, Boolean) :阻止当前线程,直到当前实例收到信号,使用 TimeSpan 度量时间间隔并指定是否在等待之前退出同步域。   
     WaitAll():等待全部信号。
  实现

      

 class Program
    {

        static void Main()
        {
            Request req = new Request();

            //这个人去干三件大事  
            Thread GetCarThread = new Thread(new ThreadStart(req.InterfaceA));
            GetCarThread.Start();

            Thread GetHouseThead = new Thread(new ThreadStart(req.InterfaceB));
            GetHouseThead.Start();

            //等待三件事都干成的喜讯通知信息  
            AutoResetEvent.WaitAll(req.autoEvents);

            //这个人就开心了。  
            req.InterfaceC();

            System.Console.ReadKey();
        }
    }

    public class Request
    {
        //建立事件数组  
        public AutoResetEvent[] autoEvents = null;

        public Request()
        {
            autoEvents = new AutoResetEvent[]
            {
                new AutoResetEvent(false),
                new AutoResetEvent(false)
            };
        }

        public void InterfaceA()
        {
            System.Console.WriteLine("请求A接口");

            Thread.Sleep(1000*2);

            autoEvents[0].Set();

            System.Console.WriteLine("A接口完成");
        }

        public void InterfaceB()
        {
            System.Console.WriteLine("请求B接口");

            Thread.Sleep(1000 * 1);

            autoEvents[1].Set();

            System.Console.WriteLine("B接口完成");
        }

        public void InterfaceC()
        {
            System.Console.WriteLine("两个接口都已经请求完,正在处理C");
        }
    }

 

  注意,WaitOne 或是WaitAll 最好都加上超时时间。否则没有收到信号,线程一直会阻塞。
 
  后话
  这个只是上面的场景的一个简化,主要是用来解决刚刚我说的那个场景的问题。
  以上是自己对 AutoResetEvent 的使用总结。不足之处请各位指点一二。