【问题标题】:F# syntax for P/Invoke signature using MarshalAs使用 MarshalAs 的 P/Invoke 签名的 F# 语法
【发布时间】:2009-11-06 18:35:32
【问题描述】:

我不确定它的语法。我正在尝试将此 C# 代码翻译成 F#。

struct LASTINPUTINFO
{
    public uint cbSize;
    public uint dwTime;
}

public class IdleTimer
{
    [DllImport("User32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
}

这是我目前所拥有的。

type LASTINPUTINFO = {
    cbSize : UInt32;
    dwTime : UInt32;
}

type IdleTimer =
    [<DllImport("User32.dll")>]
    [<return: MarshalAs(UnmanagedType.Bool)>]
    extern GetLastInputInfo(plii : LASTINPUTINFO ref)

【问题讨论】:

    标签: winapi f# pinvoke marshalling


    【解决方案1】:

    除了 Brian 的评论之外,可能值得指出的是 F# extern 签名相当忠实地反映了 C 签名,因此您可以将参数声明为 LASTINPUTINFO* plii,而不是在引用上使用 [&lt;In&gt;][&lt;Out&gt;] 属性,然后在调用函数时使用 && 运算符传递对本地实例的引用。

    【讨论】:

      【解决方案2】:

      老实说,我没有尝试运行或使用它,但它可以编译,希望能引导您朝着正确的方向前进。

      open System
      open System.Runtime.InteropServices 
      
      [<Struct>]
      type LASTINPUTINFO = 
          val cbSize : UInt32
          val dwTime : UInt32
      
      module IdleTimer =
          [<DllImport("User32.dll")>]
          extern [<MarshalAs(UnmanagedType.Bool)>] bool GetLastInputInfo([<In>][<Out>] LASTINPUTINFO plii)
      

      【讨论】:

        【解决方案3】:

        除了 kvb 的评论,我发现将参数声明为指针会弄乱当前的 FSharp Power Tools 重构引擎。您可以使用IntPtr 来解决这个问题:

        open System
        open System.Runtime.InteropServices 
        open Microsoft.FSharp.NativeInterop
        
        [<Struct>]
        type LASTINPUTINFO = 
          val mutable cbSize : uint32
          val dwTime : uint32
        
        [<DllImport("user32.dll")>]
        extern bool GetLastInputInfo(IntPtr p)
        
        let getLastInputTime() = 
          let mutable time = LASTINPUTINFO(cbSize = 8u)
          GetLastInputInfo(NativePtr.toNativeInt &&time) |> ignore
          time.dwTime
        

        【讨论】:

          【解决方案4】:

          更新的答案:

          在大多数情况下,使用 P/Invoke 时,可以简单地从 C 头文件中复制并粘贴签名(当然,没有分号)。但是,至少在一种情况下,天真地这样做会产生无法验证类型安全的代码。我们来看一个具体的例子。给定 C 中的以下函数原型:

          __declspec(dllexport) void getVersion (int* major, int* minor, int* patch);
          

          可以在 F# 中使用以下 P/Invoke 签名(和相关调用):

          [<DllImport("somelib",CallingConvention=CallingConvention.Cdecl)>]
          extern void getVersion (int* major, int* minor, int* patch)
          
          let mutable major,minor,patch = 0,0,0
          getVersion(&&major,&&minor,&&patch)
          printfn "Version: %i.%i.%i" major minor patch
          

          但是,这并不完全正确。事实证明,在处理 CLR 时,有两种类型的指针:非托管和托管。后者是您在通过引用传递 CLR 类型时使用的(即 F# 中的“byref”,C# 中的“ref”,或 VB 中的“ByRef”)。如果您希望您的 F# 代码是可验证的类型安全的,那么您也应该使用托管类型——这包括 P/Invoke 调用。如果你仔细想想,这是有道理的。运行时只能保证它可以控制的位(即“管理”的部分)。下面是使用托管指针代替 F# 代码的样子:

          [<DllImport("somelib",CallingConvention=CallingConvention.Cdecl)>]
          extern void getVersion (int& major, int& minor, int& patch)
          
          let mutable major,minor,patch = 0,0,0
          getVersion(&major,&minor,&patch)
          printfn "Version: %i.%i.%i" major minor patch
          

          方便的桌子:

          Pointer    F#             Type Declaration      Invocation
          Unmanaged  nativeint      <type>*               &&<type>
          Managed    byref <type>   <type>&               &type
          

          在几乎所有情况下,.NET 开发人员都应该更喜欢托管指针。将未管理的风险留给 C 代码。

          编辑来源:P/Invoke Gotcha in f#

          作为额外说明,要作为 byref 传入,变量必须标记为可变。传递一个非可变对象,即使具有可变属性,也是一个只读的 inref。通过引用传递只读值类型很方便。 F# ByRef and InRef

          【讨论】:

            猜你喜欢
            • 2013-06-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多