在WPF中无操作后返回

在很多时候,我们需要使用WPF来实现一些程序,例如展示程序。最近我就做了这样的程序。但是有一个问题,就是如果做展示程序(类似触摸屏的那种),那么有必要让程序在无操作后返回到一个类似屏保的界面(可以参考windows的屏幕保护或者自动锁定)。很遗憾,我没有在wpf中发现具有这样功能的类/函数,只能转向求助于windows API。

在windows api中(“user32.dll”),有一个名为“ GetLastInputInfo”的API,在MSDN上,它的解释是这样的:

This fundtion is useful for input idle detection. However, GetLastInputInfo does not provide system-wide user input information across all running sessions. Rather, GetLastInputInfo provides session-specific user input information for only the session thar invoked the function.

The tick count when the last input event was received (see LastInputInfo) is not guaranteed to be incremental. In some cases, the value might be less than the tick count of a prior event. For example, this can be caused by a timing gap between the raw input thread and the desktop thread or an event raised by SendInput, which supplies its own tick count.

上面第一段话的意思是,GetLastInputInfo并不提供全局(在整个桌面环境中)的输入信息,而是提供特定的会话的输入信息。

第二段的意思是,接收到的输入时间的tick count并不是完全准确的,在某些情况下,可能会比实际的值要小,这种情况可能会由纯输入线程和桌面线程之间的时间差,或者是由SendInput引起。

_注:由于个人水平有限,以上引语的翻译可能不准确,所以最好阅读原文。另外,以上内容不会随着其出处更新而更新,用时请参考其出处_

所以最终我选用这个GetLastInputInfo,在C#/WPF中写好引用即可。
下面是本功能的核心代码

using System.Runtime.InteropServices; // 如果需要调用外部的dll的话,这个引用是必要的
using System.Windows;
using System.Windows.Threading;  // 此处是为了使用WPF的DispatcherTimer,
                                                                 // 如果你需要使用其他的Timer,请自行using
namespace
{
    public class TestClass
    {
        private DispatcherTimer timer; // 我们需要使用的timer,在timer里面我们要检测上一次操作的信息
        private readonly static int defultIdleAllowed = 5; // 默认的允许idle时间(单位为s),在这个时间里,如果没有操作,那么就返回,或者执行其他的操作
        private static int currentIdleTimeRemained = defaultIdleAllowed; // 当前剩余的可以idle的时间,单位为s

        public TestClass()
        {
            timer = new DispatcherTimer();
            timer.Tick += new EventHandler(Timer_Tick);
            timer.Interval = new TimeSpan(0, 0, 1);
            timer.Start();
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            if(HasActionInLastSecond())
            {
                if(--currentIdleTimeRemained == 0)
                {
                    currentIdleTime = defaultIdleAllowed;
                    timer.Stop();

                    // 在这里你可以做一些类似返回主屏幕的操作

                    timer.Start();
                }
            }
            else
            {
                currentIdleTime = defaultIdleAllowed;
            }
        }

        /// <summary>
        /// 未操作时间超过1s才开始计数
        /// </summary>
        /// <returns>bool,表示未操作时间是否超过1s</returns>
        private bool HasActionInLastSecond()
        {
            return GetIdleTime() / 1000 > 1;
        }

        /// <summary>
        /// 通过window api获得上次键鼠操作的时间,返回值为ms
        /// </summary>
        /// <param name = "pLastInputInfo">ref, 存放时间的一个结构体,在下文中会有定义</param>
        /// <returns></returns>
        [DllImport("user32.dll")]
        static extern bool GetLastInputInfo(ref PLASTINPUTINFO pLastInputInfo);

        private static long GetIdleTime()
        {
            PLASTINPUTINFO pLastInputInfo = new PLASTINPUTINFO;
            pLastInputInfo.cbSize = Marshal.SizeOf(pLastInputInfo);
            if(!GetLastInputInfo(ref pLastInputInfo))
            {
                return 0;
            }
            return Environment.TickCount- pLastInputInfo.dwTime;
        }

        // PLASTINPUTINFO结构体定义
        [StructLayout(LayoutKind.Sequential)]
        private struct PLASTINPUTINFO
        {
            [MarshalAs(UnmanagedType.U4)]
            public int cbSize;
            [MarshalAs(UnmanagedType.U4)]
            public uint dwTime;
        }

【教程】关于C#中调用Cpp编写的dll

dll文件
dll文件用的图片

前几天写了一篇文章,讲了如何建立dll以及如何调用dll,文章中使用的是C/C++,今天就说说怎么使用C#调用C++的dll。


首先建立一个C#工程。然后再原文件夹中的Debug文件夹中放入我们做好的dll,这里我们使用前一篇文章中建立的dll:“basic_dll.dll”,然后就是写代码。

在C#中如果要调用dll,就要using一个空间:System.Runtime.InteropServices,包含进来就好了。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

这里包含进了一些无关的东西,不用管它,我一建立工程就有这些了,懒得删-_-……

如果要载入dll,就要在声明方法之前加上


[DllImport("basic_dll.dll", EntryPoint = "add",CallingConvention = CallingConvention.Cdecl)]

其中,“basic_dll.dll”是我们要引入的dll的名字,要么它在程序的同一目录下,要么在系统中已经注册过,后面的“EntryPoint”是dll的入口函数,也就是我们要导入的函数名称,再后面的CallingConvention是调用约定,如果不加上这个调用约定的话,会导致编译时出现”对 PInvoke 函数“xxFunction()”的调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配“这样的错误,原因一会说。

然后的然后就是声明一下我们的函数了,在这里同样只有一个add(int, int)   -_-

一下是一段简单到要死的源代码


namespace Csharp调用
{

class Program
{
[DllImport("basic_dll.dll", EntryPoint = "add",CallingConvention = CallingConvention.Cdecl)]
public static extern int add(int a, int b);

static void Main(string[] args)
{
Console.WriteLine(add(3, 5));
Console.ReadKey();
}
}
}

这段代码极其简单以至于我就跳过解读了,然后说说关于刚才提到的堆栈不对称的问题。

众所周知,在c++WIN32程序中有三种calling convention(调用约定):__cdecl, __stdcall, __fastcall,默认为__cdecl。而c#中默认为CallingConvention =CallingConvention.Winapi,如果不对调用约定进行说明的话,会出现调用约定不一致的错误。当然,在dll中,要传出的方法应该放在extern ”C“中,不然会因为C++更改函数名而导致调用错误。

【教程】dll动态链接库学习第一课:使用C++创建并调用dll

很长时间以来都总想着要写技术文章,今天就先了了这个想法,写一篇关于动态链接库学习的感想。

关于dll动态链接库的介绍我就不说了,百度上360百科上写的很详细。之前看过《Windows核心编程(第五版)》,但是无奈里面写的有些晦涩。


 

废话少说,先说说怎么写一个dll。

先打开vs或者vc,新建一个Win32项目(具体的方法:新建项目->Visual C++ -> Win32 ->Win32项目),然后名称和位置填上(注意名称中最好只包括英文符号,不然极有可能出现错误)。我新建的名字是basic_dll

然后在弹出的框中点下一步,然后在“应用程序类型”中选择“DLL”,在“附加选项”中,勾上“导出符号”,完成即可。

然后剩下的事情就是编写你的文件了。在这里我只在里面放一个

int add(int, int);

相信大家看到名字也就能猜出来他是干什么的了!没错,就是返回两个int的和。具体怎么做呢?

在“basic.h”(<工程名字>就是你刚刚填写的名称,一下均遵从次约定)的最后,添加上这样一句话:

BASCI_DLL_API int add(int, int);

这句话中,BASIC_DLL_API的定义参见上面的#ifdef块。然后打开basic_dll.cpp,在里面添加定义

BASIC_DLL_API int add(int a, int b)

{

int ans = a + b;

return ans;

}

然后我们似乎大功告成啦!先生成……

啊嘞?怎么提示“无法启动程序”?

如果看到这个的话,你一定是点了调试(按F5或者是那个绿色的三角),别担心。首先,dll本身是不能独立运行的,其次,我只让你生成,没让你调试啊!!!给我好好看着!

咳咳……当你看到“1成功”的时候,就说明已经生成了你要的dll。此时还会生成一个lib文件。

然后到你的文件目录中去寻找 basic_dll.dll、basic_dll.lib、basic_dll.h 这三个文件,一会我们要用。


 

然后咱们再说说怎么用这个dll和这个lib。

首先你得新建一个控制台程序吧~步骤我就不细说了。这里假定我们新建的工程名称是“basic_exe”。

然后是准备工作。把上面的三个文件,拷贝到你的工程目录中的basic_exe文件夹中,也就是说如果你的工程文件夹是“F:\basic_exe”,那么你应该放在“F:\basic_exe\basic_exe”中,然后再把basic_dll.dll放在“F:\basic_dll\Debug”中一份,以免一会运行的时候提示找不到dll。

然后开始写我们的程序啦。先静态调用,也就是调用lib。

先把刚才的“basic_dll.h”包含进来,当然在这里要在工程中引入这个文件了~

然后下面我们开始编写代码

#include <iostream>

#include "basic_dll.h"

using namespace std;

int main()

{

int a = 1;

int b = 2;

int ans = add(a, b);

cout << ans << endl;

return 0;

}

然后调试……

咦?怎么有一个错误?1个无法解析的外部命令?这是什么鬼?

不用担心,这是因为我们虽然告诉了编译器有一个头文件,但是没有告诉它库文件.lib在哪里啊~我们只需要把它包含进来即可。项目属性-》配置属性-》连接器-》输入-》在右边的附加依赖项中加入你的lib的地址就可以了。

但是似乎还有?

这是因为—–我们没有加上extern “C”。

我们打开刚才写的dll,然后在basic_dll.h中的函数add外面,加上extern,也就是变成这样


extern "C"
{
BASIC_DLL_API int add(int a, int b);
}

然后就OK了~

然后再调试……OK!


然后再说说动态调用dll的。

先包含进库的说


#include <Windows.h>

#include <iostream>

#include "basic_dll.h"

当然这里你也要把basic_dll.h包含进工程

然后是正文。


using namespace std;

typedef int(*MYPROC) (int, int)

int CallDll()

{

HINSTANCE hinst;

MYPROC myproc;

hinst = LoadLibrary(L"basic_dll.dll");

if (hinst != NULL)

{

myproc = (MYPROC)GetProcAddress(hinst, "add");

int a = 0;

a = myproc(3, 4);

cout << "3 + 4 =" << a << endl;

FreeLibrary(hinst);

}

else

{

cout << "加载dll失败,因为hinst空" << endl;

}

return 0;

}

int _tmain(int argc, _TCHAR* argv[])

{

CallDll();

return 0;

}

大功告成!调试……

然后我就不说了~大概就到这里,千里之行属于足下,这是dll学习的第一课~