潜水潜太久,都快被淹死了。终于鼓起勇气,挣扎着浮出水面。我不知道透一口气后还能坚持多久,总之先给大家一个还凑活的文章吧。
在说正题之前,先说明一下,我会尽量使用 F# 语言来表述相关的技术,如果对 F# 不熟悉的同学,可以参考MSDN
http://msdn.microsoft.com/zh-cn/library/dd233154.aspx
目前Silverlight OOB环境下虽然可以随意的设置窗体的位置,但是微软似乎还没有提供获取桌面可视范围的参数(屏幕分辨率减去任务栏占用的尺寸后剩下的那部分)。
微软不支持,不要紧,我们可以 OOB |> Require elevated strust when running outside the browser |> PInvoke Win32 API
我想,大部分同学玩 SL OOB 都会点上 Use GPU Acceleration 和 Require elevated strust when running outside the browser 吧。如果不知道在哪里,可以在 Solution Explorer 中选中你的 SL 项目,然后按 Alt + Enter 快捷键,然后自己找找吧。
下面我们开始 DllImport。首先 SL 的 Marshal 是不支持下面这个方法的。
IntPtr p = Marshal.AllocHGlobal(sizeof(某结构));
所以我们不得不先用 kernel32.dll 中的 LocalAlloc 来分配内存,不过一定要记得 LocalFree 哦~。~
[<DllImport("kernel32.dll")>]
extern IntPtr LocalAlloc(uint32 uFlags, UIntPtr uBytes);
[<DllImport("kernel32.dll", SetLastError=true)>]
extern IntPtr LocalFree(IntPtr hMem)
接下来,uFlags 参数可以有很多的值,一起发出来了,油多不坏菜,代码多不怕硬盘小,不过我们这里只要用到 LPTR,别的留给大家做纪念~。~
module internal SoftCat.Interop.WinBase
open System
open System.Runtime.InteropServices
#region // Local Memory Flags
let LMEM_FIXED = 0x0000u
let LMEM_MOVEABLE = 0x0002u
let LMEM_NOCOMPACT = 0x0010u
let LMEM_NODISCARD = 0x0020u
let LMEM_ZEROINIT = 0x0040u
let LMEM_MODIFY = 0x0080u
let LMEM_DISCARDABLE = 0x0F00u
let LMEM_VALID_FLAGS = 0x0F72u
let LMEM_INVALID_HANDLE = 0x8000u
let LHND = LMEM_MOVEABLE ||| LMEM_ZEROINIT
let LPTR = LMEM_FIXED ||| LMEM_ZEROINIT
let NONZEROLHND = LMEM_MOVEABLE
let NONZEROLPTR = LMEM_FIXED
#endregion
[<DllImport("kernel32.dll")>]
extern IntPtr LocalAlloc(uint32 uFlags, UIntPtr uBytes);
[<DllImport("kernel32.dll")>]
extern IntPtr LocalFree(IntPtr hMem)
注意上面的代码,我这里并没有像 C# 通常的做法,定义一个枚举来存放那么多的可选值。并且我也非常建议大家使用 F# Invoke 时直接 let 在 module 中。
||| 运算符表示位或运算,但是下面的代码是不符合 F# 目前的语法规范的。
type A =
| LPTR = 0x0002u ||| 0x0040u
好了,能分配内存了,我们接下来需要用 user32.dll 中的 SystemParametersInfo 这个强大的函数来干坏事了,嘿嘿~。~
[<DllImport("user32.dll")>]
extern bool SystemParametersInfo(uint32 uiAction, uint32 uiParam, IntPtr pvParam, uint32 fWinIni)
解释一下几个参数。uiAction 这个是 define 好的,其中我们只需要用到 SPI_GETWORKAREA,而 fWinIni 我们也只用到 0 值。
如果不想代码太多,像下面这样就可以了
let SPI_GETWORKAREA = 0x0030u
pvParam 会返回一个 在 WinDef.h 头文件中的 RECT 结构,我们要预先定义好。 uiParam 则是 RECT 所需的内存尺寸。
module internal SoftCat.Interop.WinDef
#nowarn "9"
open System
open System.Runtime.InteropServices
[<StructLayout(LayoutKind.Sequential)>]
type RECT =
{ left : int; top : int; right : int; bottom : int }
with
static member Zero = { left = 0; top = 0; right = 0; bottom = 0 }
注意,上面的 #nowarn "9",嘿嘿,去掉后报错了吧~。~自己看错误描述~。~不多解释~。~
RECT 是一个记录,然后添加了一个Zero的静态属性。在F#中,记录实际上是一个不可变类型,实现了好几个接口,比如 IComparable。嘿嘿,这语法糖很甜哦~。~
有人要问了,这应该创建一个 struct 啊,你创建 class 干吗? 因为 Marshal.PtrToStructure 这个方法有点坑爹,你可以试试改 struct 后它的反应。注意,我们现在是 Silverlight 应用程序。
==============================================
做了这么多准备工作,我们现在开始干活了,先上代码:
module SoftCat.Windows.Screen
open System
open System.Runtime.InteropServices
open System.Windows
open SoftCat.Interop
let getWorkArea () : Rect =
let size = uint32(Marshal.SizeOf(typeof<WinDef.RECT>))
let p = WinBase.LocalAlloc(WinBase.LPTR, new UIntPtr(size))
let r = WinUser.SystemParametersInfo(WinUser.SPI_GETWORKAREA, size, p, 0u)
let re =
match r with
| true ->
let rect = WinDef.RECT.Zero
Marshal.PtrToStructure(p, rect) |> ignore
WinBase.LocalFree p |> ignore
let point1 = new Point(float(rect.left), float(rect.top))
let point2 = new Point(float(rect.right), float(rect.bottom))
new Rect(point1, point2)
| false ->
WinBase.LocalFree p |> ignore
new Rect()
re
解释一下。我们只在 SoftCat.Windows.Screen 这个 module 中暴露了一个 getWorkArea 的函数。它返回了 System.Windows.Rect 这个 .net 库中的结构。 个人建议如果 .net 本身有类似的结构,最好不要返回 PInvoke 时自己定义的,因为这样调用者可以偷懒,直接用就好了,不用自己转换一次,嘿嘿。
let p = WinBase.LocalAlloc(WinBase.LPTR, new UIntPtr(size))
分配内存,记得要释放
let r = WinUser.SystemParametersInfo(WinUser.SPI_GETWORKAREA, size, p, 0u)
取工作区尺寸,数据传给 p 指向的内存空间。
let rect = WinDef.RECT.Zero
Marshal.PtrToStructure(p, rect) |> ignore
从 p 的内存空间中取出数据,然后就可以释放空间了。不得不说一个问题,这里的rect就如前面所说的是一个不可变类型,即使这样,我们也不得不承认直接操作内存的威力。
剩下的不解释,太容易了,自己看。
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var size = SoftCat.Windows.Screen.getWorkArea();
MessageBox.Show(string.Format("{0}, {1}, {2}, {3}", size.Left, size.Top, size.Right, size.Bottom));
}
}
调用这个函数,得到下面的结果,有图有真相,呵呵~