C#将C++动态库的回调函数封装成事件
关于C#调用C++动态库的文章很多,调用动态库中回调函数的方法也不在少数。但大多数调用回调函数的方法依然保留了C++的语法特点。
比如有一段C++的回调函数代码,为了表达它的意思,我把注释也粘贴了进来:
/*********************************************************************************************************
** Function name: epcBuzzerAsyncOn
** Descriptions: 本函数使蜂鸣器蜂鸣指定时间
** input parameters: dwOntime 蜂鸣器持续蜂鸣的时间(ms),其中0表示一直蜂鸣
** output parameters: 无
** Returned value: TRUE:成功;FALSE:失败
** Note: 本函数以异步方式执行,不会被阻塞
*********************************************************************************************************/
EPCBUZZERLIB_API BOOL epcBuzzerAsyncOn (DWORD dwOntime);
/********************************************************************************************************* ** Function name: epcBuzzerSetCallBackFunc ** Descriptions: 设置回调函数指针,当异步的蜂鸣器操作任务完成后,会调用该回调函数通知用户程序。 ** input parameters: lpfnNotify 回调函数指针,如果是NULL,则表示不需要通知用户程序。函数类 ** 型为void (*lpfnNotify)(BOOL bResult),bResult为执行结果, ** TRUE表示执行成功,FALSE表示执行失败 ** output parameters: 无 ** Returned value: 无 *********************************************************************************************************/ EPCBUZZERLIB_API VOID epcBuzzerSetCallBackFunc( void (*lpfnNotify)(BOOL));
一般的文章都会教你转换成如下代码,这些方法都属于Buzzer类:
/// <summary> /// 使蜂鸣器蜂鸣指定时间,异步执行,不会阻塞当前线程. /// </summary> /// <param name="onTime">蜂鸣时间(ms).</param> /// <returns>true:蜂鸣成功,false:蜂鸣失败</returns> [System.Runtime.InteropServices.DllImport("Lib\\epcBuzzerLib.dll", SetLastError = true)] private static extern bool epcBuzzerAsyncOn(int onTime); /// <summary> /// 先申明一个委托,该委托定义了蜂鸣完成回调函数的指针和数据类型 /// </summary> /// <param name="result">if set to <c>true</c> [result].</param> public delegate void BuzzerHandler(bool result); /// <summary> /// 再设置蜂鸣完成回调函数. /// </summary> /// <param name="handler">The handler.</param> [System.Runtime.InteropServices.DllImport("Lib\\epcBuzzerLib.dll", SetLastError = true)] private static extern void epcBuzzerSetCallBackFunc(BuzzerHandler handler);
然后在主程序中如此调用:
//先定义一个满足委托类型的函数 public void BuzzerNotify(bool result) {
//在这里编写蜂鸣完成后你想要执行的代码...我这里根据result随便写几行。 if(result) { MessageBox.Show("蜂鸣成功"); } else { MessageBox.Show("蜂鸣失败"); } } //在按钮事件中调用回调函数 private void onButton_Click(object sender, EventArgs e) { Buzzer.epcBuzzerCallBackFunc(this.BuzzerNotify); Buzzer.epcBuzzerAsyncOn(3000); }
这样,在蜂鸣器蜂鸣3000ms之后,就会调用BuzzerNotify,并将一个bool结果传进去。然后你可以在BuzzerNotify中做你想做的事。
虽然这样也能够解决问题,但是我总感觉很别扭,总觉得我的代码里有C++风格,这让有代码洁癖的我感觉到浑身不自在。所以,我决定重构一下这段代码,让它符合C#的风格。
重构的思想是这样的,虽然我不太能完全理解回调函数这个东西,但是我能隐约感觉到它就是一个事件触发机制,比如上面那段代码,就可以理解为当蜂鸣完成后,就会通知BuzzerNotify,让它开始执行。只不过BuzzerNotiy是在主函数中定义的一个函数,它不能像事件那样可以注册,你所有想在蜂鸣器完成后做的工作,都必须写在这个函数里面。很明显,如此糟糕的扩展性,完全不是C#的风格。所以,我要做的,就是将BuzzerNotify变成Buzzer类的一个事件,让它可以使用+=这样的神器!
其实做法也是蛮简单的,我把代码一贴出来大家立马就能明白了。以下是Buzzer类的代码
/// <summary> /// 申明蜂鸣完成回调函数指针和数据类型 /// </summary> /// <param name="result">if set to <c>true</c> [result].</param> public delegate void BuzzerHandler(bool result); /// <summary> /// 蜂鸣完成时触发该事件. /// </summary> public static event BuzzerHandler BuzzingComplete; /// <summary> /// 使蜂鸣器蜂鸣指定时间,异步执行,不会阻塞当前线程. /// </summary> /// <param name="onTime">蜂鸣时间(ms).</param> /// <returns>true:蜂鸣成功,false:蜂鸣失败</returns> public static bool TurnOn(int time) { Buzzer.BuzzingCompleteSetCallBack(); return Buzzer.epcBuzzerAsyncOn(time); } /// <summary> /// 使蜂鸣器蜂鸣指定时间,异步执行,不会阻塞当前线程. /// </summary> /// <param name="onTime">蜂鸣时间(ms).</param> /// <returns>true:蜂鸣成功,false:蜂鸣失败</returns> [System.Runtime.InteropServices.DllImport("Lib\\epcBuzzerLib.dll", SetLastError = true)] private static extern bool epcBuzzerAsyncOn(int onTime); /// <summary> /// 定义蜂鸣完成回调函数,用来处理事件.这个方法比较关键,它相当于把之前的BuzzerNotify,只不过把它封装进Buzzer类了。 /// </summary> /// <param name="result">if set to <c>true</c> [result].</param> private static void Event(bool result) { if (BuzzingComplete != null) { BuzzingComplete(result); } } /// <summary> /// 蜂鸣完成回调函数. /// </summary> private static void BuzzingCompleteSetCallBack() { Buzzer.epcBuzzerSetCallBackFunc(Event); } /// <summary> /// 设置蜂鸣完成回调函数. /// </summary> /// <param name="handler">The handler.</param> [System.Runtime.InteropServices.DllImport("Lib\\epcBuzzerLib.dll", SetLastError = true)] private static extern void epcBuzzerSetCallBackFunc(BuzzerHandler handler);
虽然多了很多代码,但为了面向对象,这绝对是值得的!
在主函数中可以如此调用:
private void FirstThing(bool result) { MessageBox.Show("Do my first thing"); } private void SecondThing(bool result) { MessageBox.Show("Do my second thing"); } private void onButton_Click(object sender, EventArgs e) { Buzzer.BuzzingComplete += this.FirstThing; Buzzer.BuzzingComplete += this.SecondThing; Buzzer.TurnOn(3000); }
如此一来,主程序中的代码看上去就舒服多了。值得一提的是,如果你连续点几次这个OKButton的话,这两个方法会被重复注册。