上一篇中我们介绍了凭据的加载和代码中函数的调用顺序,接下来我们就要了解一下一些关键函数在代码中起到什么作用了。了解清楚这些以后我们才能定制出我们自己需要功能。
CSampleProvider::SetUsageScenario
这个函数非常重要,在凭据被加载起来以后,由微软调用,我们实现这个函数里面的功能,微软调用时会给函数传递两个参数,如下所示:
1 | HRESULT CSampleProvider::SetUsageScenario( |
其中 dwFlags
函数我们不需要关心,着重要关注的是 cpus
参数,这个参数标志了系统是锁屏、还是开机时登录而调用的凭据。如果是锁屏,那么 cpus 的值等于 CPUS_UNLOCK_WORKSTATION
,而如果是开机登陆(或切换用户)则 cpus 的值等于 CPUS_LOGON
。通过判断不同的登录类型,我们来给使用者显示不同的界面。而微软的例子中是将两中登录类型都同时创建了一个凭据,看如下代码:
1 | // SetUsageScenario is the provider's cue that it's going to be asked for tiles |
示例中在登录和锁屏的两种情况都创建创建了 CSampleCredential
对象,这个对象就是实现凭据页面具体功能的对象。如果你需要区分登录和锁屏,那么在这里做区分创建不同的凭据对象,或者在凭据对象中判断 _cpus 的值(这个值被用作第一个参数传递到凭据对象中了)来显示不同的控件。
CSampleCredential::Initialize
注意,这里我们切换到了 CSampleCredential 类中,因为在上面介绍的方法中创建了一个 CSampleCredential 对象,并调用了该对象的 Initialize 方法,这个方法就实现了初始化凭据页面控件文字和数据的功能。同时,在调用这个方法时传递了三个参数,第一个参数就是我们刚才说的 _cpus,第二个参数描述了要创建的控件类型及控件初始化文字,第三个参数描述了创建的这些控件的初始状态,是显示、隐藏、还是具备焦点等。
1 | // Initializes one credential with the field information passed in. |
在 CSampleCredential::Initialize 函数中,遍历了这两个参数,并将这两个参数传递的内容保存到了自己类中的成员变量 _rgCredProvFieldDescriptors 和 _rgFieldStatePairs 中,这两个变量在初始化时与 CSampleProvider 初始化使用的都是相同的枚举。所以长度、成员类型、数量都是一样的。
1 | // Initializes one credential with the field information passed in. |
代码中我们可以看到,还有一个 _rgFieldStrings 的成员,是一个字符串指针数组变量,它是为了存储每个控件的文字信息,与 _rgCredProvFieldDescriptors 变量配合使用。给每隔字符串指针数组成员赋值后,初始化结束了。
CSampleCredential::SetSelected
在初始化完成后,我们后续会看到一系列对控件初始化的一些操作,这些函数我们不必过度的去关心他,自己下个断点跟踪一下,就知道具体的执行过程了。接下来我们要介绍的这个函数就是在控件都初始化完毕后,你可能要在控件显示之前根据业务的不同情况对控件做一些改变,比如我们希望如果当前是锁屏而调用的凭据,那么我们只显示一个密码输入框,不需要显示用户名输入框了,因为锁屏的时候你可以通过代码判断出当前会话锁屏的用户信息。而如果是登录或切换用户而调用的凭据,那么我们要显示用户名和密码的输入框。当然这只是一个简单的业务场景描述,大家根据自己业务需求的不同即可在这个函数对控件的显示和隐藏做手脚。在这个函数操作控件前,你要先判断 _pCredProvCredentialEvents 成员是否是有效的,接着调用 _pCredProvCredentialEvents 的一些方法来对控件设置状态或文字等信息。如下所示:
1 | // LogonUI calls this function when our tile is selected (zoomed) |
上面代码仅作示例,可能并没有什么实际作用。大家可能也注意到了 pbAutoLogon 参数,这个参数是一个传出参数,当你将它的值设置为 TRUE 的时候,系统将会尝试自动登录。这也是一个非常重要的特性,这里自动登录后,将直接触发我们下面要介绍的函数 GetSerialization。
CSampleCredential::GetSerialization
该函数就是界面上点击登录按钮,或者上面我们提到自动登录后触发的函数,再这里,你需要将界面上输入的用户名及密码等信息传递给系统,让操作系统去执行登录的操作。如下代码所示:
1 | // Collect the username and password into a serialized credential for the correct usage scenario |
函数中调用了获取计算机名的 API,并调用几个功能函数填充了登录系统所需的结构体,传递给系统进行登录。填充结构体的几个功能函数大家可以自己看一看,并不复杂。
CSampleCredential::ReportResult
ReportResult 函数是我们点击确定按钮登录系统后,操作登录反馈给我们结果的函数。你的登录成功了、密码过期了、密码错误了等信息都可以通过这个函数捕获到,配合上面的 GetSerialization 函数你可以完成一系列非常严谨的身份认证功能。ReportResult 函数有 4 个参数。
1 | HRESULT CSampleCredential::ReportResult( |
当你在使用的时候,建议你在该函数的入口处增加一处日志,打印出 ntsStatus 和 ntsSubStatus 的值。这样在遇到一些没遇到过的错误时,可以通过日志来分析问题。示例代码中给我们提供了两种错误示例:
1 | static const REPORT_RESULT_STATUS_INFO s_rgLogonStatusInfo[] = |
一种是登录失败的错误码,一种是用户被禁用的错误码。如果想知道更多的错误码,比如密码过期等,可以从这两个宏跟进去就能看到所有的错误码了。最终你可以根据这些错误码给出不同的提示,当然提示的字符串 ppwszOptionalStatusText 也是可以修改的,你只需要调用 SHStrDupW 函数向这个字符串填充一些你想提示的字符串即可。调用前别忘记释放这个字符串的内存哦。