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++ 动态库,还有一个特别麻烦的问题就是参数对于的问题。后续会结合网上的资料总结一份详细的对照表。

 

如何内网搭建NuGet服务器

  NuGet 是.NET程序员熟知的工具,它可以直接安装开源社区中的各个公用组件,可以说是非常方便。不过,有些时候,公司内部的公用的基础类库,各个项目都有引用,但又不能发布到公共的NuGet服务器上,所以我们需要自己架设一个私有服务,通过NuGet 来的各个项目中用到的公共组件进行管理。就非常的方便。也便于组件版本的管理。

 

一、NuGet网站构建

  1. 创建一个空的ASNET网站

   

   注意:将Framework 设置为4.6 以上,NuGet.Server库 要求.net framework 的版本是4.6 。

 

  2. 用NuGet为 NuGetServer 安装“NuGet.Server”库。
 
  安装成功后,你的Solution会变成这样一个结构。并且不需要写任何代码或更改任何配置,它已经可以运行了。运行此网站,如果看到下面的欢迎界面,就表示网站已经构建完成了。
   
   注意:Packages里只有一个readme.txt,这个目录是用来放NuGet包的,点击 here 连接,可以查看现有的包。

  3. 部署网站

  这也和部署任何一个ASP.NET网站完全一样。这里不细说。
 

二、把代码打包为NuGet包

  1. 去Nuget官网下载nuget command line tool,

   下载地址:https://www.nuget.org/downloads

 

  2. 用Nuget打包

   Nuget 有两种打包方式:

   A:直接通过工程文件打包dll:

     1 创建一个类库,然后编译下。把刚才下载的nuget.exe拷贝到工程文件所在的文件夹中:

       

    2. 通过命令行来创建nuget包:nuget.exe pack

     

 

  B:使用nuget的spec文件进行打包,。

    1. 同样把要打包的DLL跟NuGet.exe放在一个目录下,用nuget spec命令创建一个nuspec文件:

    

 

    2. 打开Package.nuspec文件,可自定义一些配置,。

     注意$description$使用AssemblyDescriptionAttribute的值进行替换,在产生package之前,一定要记得先编译项目,否则会提示$description$找不到的错误;

 

    3. 用 NuGet pack打包成Nuget包:

     

 

 

  至此打包完毕。

 

三、 把NuGet包上传到服务器

  将打包好的.nupkg 文件 拷贝到 第一步中架好的Nuget服务器 的Package目录下。可以Nuget 服务中自己写一个文件上传的功能,来降低拷贝的麻烦。

四、配置Visual Studio

  Nuget 服务已经架设成功,接下来就要在VS里配置NuGet 源,让其他的项目可以使用我们的NuGet服务器。
  选择工具-》Nuget包管理器-》程序包管理控制台,点击 配置图标:

   

添加,设置本地的NuGet源名字和地址即可。注意:source 最后是 /nuget

  完成后,我们的VS就可以从私服上安装NuGet包了。

五、在其他项目中使用 

  到目前为止NuGet服务器端已经配置完成,类库和DLL文件的打包也介绍完毕, 接下来我们该如何在项目中引用内网NuGet 服务器中的包呢,步骤如下:

  1、 新建一个NuGetTest 项目,然后 工具-》Nuget包管理器-》程序包管理控制台 ,在命令行:install-package IKSDK 

 

  2、最后看看效果啦:

 

至此,内网搭建NuGet服务器 的过程就介绍完了,,

 

 

C#总结(三)DataGridView增加全选列

最近的一个winform的项目中,碰到datagridview控件的第一列添加全选的功能,通常这个功能,有两种实现方式:1. 为控件添加DataGridViewCheckBoxColumn来实现,但是需要提供全选反选功能,2. 再加一个checkbox控件跟datagridview组合来实现全选反选功能。但是,感觉这两种实现效果都不是很好。网上查资料,发现一个老外的实现方法,比较简单通用。demo 代码最下面的连接给出。

他的实现方式就是:DataGridViewCheckBoxColumn的父类DataGridViewColumnHeaderCell 里面有个HeaderCell的属性,看下DataGridViewColumnHeaderCell 的继承关系,就可以知道它继承自DataGridViewCell类, 所以只需要重写DataGridViewColumnHeaderCell类的paint方法,用CheckBoxRenderer画一个Checkbox到单元格上。即可实现在datagridview的列头增加一个全选的checkbox 。以下是实现代码:

实现代码

public delegate void CheckBoxClickedHandler(bool state);
    public class DataGridViewCheckBoxHeaderCellEventArgs : EventArgs
    {
        bool _bChecked;
        public DataGridViewCheckBoxHeaderCellEventArgs(bool bChecked)
        {
            _bChecked = bChecked;
        }
        public bool Checked
        {
            get { return _bChecked; }
        }
    }
    class DatagridViewCheckBoxHeaderCell : DataGridViewColumnHeaderCell
    {
        Point checkBoxLocation;
        Size checkBoxSize;
        bool _checked = false;
        Point _cellLocation = new Point();
        System.Windows.Forms.VisualStyles.CheckBoxState _cbState = 
            System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal;
        public event CheckBoxClickedHandler OnCheckBoxClicked;
 
        public DatagridViewCheckBoxHeaderCell()
        {           
        }

        protected override void Paint(System.Drawing.Graphics graphics, 
            System.Drawing.Rectangle clipBounds, 
            System.Drawing.Rectangle cellBounds, 
            int rowIndex, 
            DataGridViewElementStates dataGridViewElementState, 
            object value, 
            object formattedValue, 
            string errorText, 
            DataGridViewCellStyle cellStyle, 
            DataGridViewAdvancedBorderStyle advancedBorderStyle, 
            DataGridViewPaintParts paintParts)
        {
            base.Paint(graphics, clipBounds, cellBounds, rowIndex, 
                dataGridViewElementState, value, 
                formattedValue, errorText, cellStyle, 
                advancedBorderStyle, paintParts);
            Point p = new Point();
            Size s = CheckBoxRenderer.GetGlyphSize(graphics, 
            System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);
            p.X = cellBounds.Location.X + 
                (cellBounds.Width / 2) - (s.Width / 2) ;
            p.Y = cellBounds.Location.Y + 
                (cellBounds.Height / 2) - (s.Height / 2);
            _cellLocation = cellBounds.Location;
            checkBoxLocation = p;
            checkBoxSize = s;
            if (_checked)
                _cbState = System.Windows.Forms.VisualStyles.
                    CheckBoxState.CheckedNormal;
            else
                _cbState = System.Windows.Forms.VisualStyles.
                    CheckBoxState.UncheckedNormal;
            CheckBoxRenderer.DrawCheckBox
            (graphics, checkBoxLocation, _cbState);
        }

        protected override void OnMouseClick(DataGridViewCellMouseEventArgs e)
        {
            Point p = new Point(e.X + _cellLocation.X, e.Y + _cellLocation.Y);
            if (p.X >= checkBoxLocation.X && p.X <= 
                checkBoxLocation.X + checkBoxSize.Width 
            && p.Y >= checkBoxLocation.Y && p.Y <= 
                checkBoxLocation.Y + checkBoxSize.Height)
            {
                _checked = !_checked;
                if (OnCheckBoxClicked != null)
                {
                    OnCheckBoxClicked(_checked);
                    this.DataGridView.InvalidateCell(this);
                }
                
            } 
            base.OnMouseClick(e);
        }     
    }

调用方式

DataGridViewCheckBoxColumn colCB = new DataGridViewCheckBoxColumn();
DatagridViewCheckBoxHeaderCell cbHeader = new DatagridViewCheckBoxHeaderCell();
colCB.HeaderCell = cbHeader;
datagridview1.Columns.Add(colCB);
cbHeader.OnCheckBoxClicked += 
    new CheckBoxClickedHandler(cbHeader_OnCheckBoxClicked);

  1. 我们只需要定义一个DataGridViewCheckBoxColumn。

2. 然后为每一行的checkbox 定义一个CheckboxClicked 事件。

 

测试程序

创建一个Winform 项目,加个datagridview控件,初始化几行默认数据。注意:datagirdview有编辑状态,如果有一行数据在编辑状态,那这一行被编辑。

解决办法就是在事件的绑定方法里面增加EndEdit()调用。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            InitDtSource();
        }

        private void cbHeader_OnCheckBoxClicked(bool state)
        {
            //这一句很重要结束编辑状态
            dgInfo.EndEdit();
            dgInfo.Rows.OfType().ToList().ForEach(t => t.Cells[0].Value = state);
        }

        private void InitDtSource()
        {
            try
            {
                var _dtSource = new DataTable();
                //1、添加列
                _dtSource.Columns.Add("姓名", typeof(string)); //数据类型为 文本
                _dtSource.Columns.Add("身份证号", typeof(string)); //数据类型为 文本
                _dtSource.Columns.Add("时间", typeof(string)); //数据类型为 文本
                _dtSource.Columns.Add("地点", typeof(string)); //数据类型为 文本

                for (int i = 0; i < 10; i++)
                {
                    DataRow drData = _dtSource.NewRow();
                    drData[0] = "test" + i;
                    drData[1] = "35412549554521263" + i;
                    drData[2] = "2017-05-21 10:55:21";
                    drData[3] = "北京市";
                    _dtSource.Rows.Add(drData);
                }

                dgInfo.DataSource = _dtSource;

                InitColumnInfo();
            }
            catch (Exception ex)
            {

            }
        }

        private void InitColumnInfo()
        {
            int index = 0;

            DataGridViewCheckBoxColumn colCB = new DataGridViewCheckBoxColumn();
            DatagridViewCheckBoxHeaderCell cbHeader = new DatagridViewCheckBoxHeaderCell();
            colCB.HeaderCell = cbHeader;
            colCB.HeaderText = "全选";
            cbHeader.OnCheckBoxClicked += new CheckBoxClickedHandler(cbHeader_OnCheckBoxClicked);
            dgInfo.Columns.Insert(index, colCB);


            index++;
            dgInfo.Columns[index].HeaderText = "姓名";
            dgInfo.Columns[index].Width = 90;

            index++;
            dgInfo.Columns[index].HeaderText = "身份证号";
            dgInfo.Columns[index].Width = 120;

            index++;
            dgInfo.Columns[index].HeaderText = "时间";
            dgInfo.Columns[index].Width = 150;

            index++;
            dgInfo.Columns[index].HeaderText = "地点";
            dgInfo.Columns[index].Width = 100;

            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
            dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;//211, 223, 240
            dataGridViewCellStyle2.ForeColor = System.Drawing.Color.Blue;
            dataGridViewCellStyle2.SelectionForeColor = System.Drawing.Color.Blue;
            dgInfo.Columns[index].DefaultCellStyle = dataGridViewCellStyle2;
        }
    }

 

其他

1. 参考地址:https://www.codeproject.com/Articles/20165/CheckBox-Header-Column-For-DataGridView

2. Demo下载

 

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 的使用总结。不足之处请各位指点一二。
 

使用.net Stopwatch class 来分析你的代码

  当我们在调试,优化我们的代码的时候,想知道某段代码的真正的执行时间,或者我们怀疑某段代码,或是某几段代码执行比较慢,

需要得到具体的某段代码的具体执行时间的时候。有一个很好用的类Stopwatch。

Stopwatch 类在 System.Diagnostics命名空间下。可以用来做分析.net代码块的基本工具。

例如:

System.Diagnostics.Stopwatch timerObj = new System.Diagnostics.Stopwatch();

timerObj.Start();

Decimal totalDec = 0;
int limit = 1000000;
for (int i = 0; i < limit; ++i) 
{
totalDec = totalDec + (Decimal)Math.Sqrt(i);
}
timerObj.Stop();
Console.WriteLine(“Sum of square roots: {0}”,totalDec);
Console.WriteLine(“Milliseconds elapsed : {0}”,timerObj.ElapsedMilliseconds);
Console.WriteLine(“Time elapsed : {0}”, timerObj.Elapsed);

 

输出结果:
Sum of square roots : 666666166.45882210823608
Milliseconds elapsed: 282
Time elapsed : 00:00:00.2828692

当你用Stopwatch 来调试你的时候,你可以使用 IDisposable 接口来自动关掉Stopwatch
1.定义一个自定义的Stopwatch类,继承System.Diagnostics.Stopwatch 和 IDisposable

class AutoStopwatchDemo : System.Diagnostics.Stopwatch, IDisposable
{
public AutoStopwatchDemo()
{
Start();
}
public void Dispose()
{
Stop();
Console.WriteLine(“Elapsed : {0}”, this.Elapsed);
}
}

 

2. 在你要调试的代码块里面使用

using (new AutoStopwatchDemo())
{
Decimal totalObj2 = 0;
int limitObj2 = 1000000;
for (int i = 0; i < limit2; ++i)
{
totalObj2 = limitObj2 + (Decimal)Math.Sqrt(i);
}
}

 

此外,Stopwatch除了有Start()和Stop()方法,还有 Reset() 方法,