F#:使用自定义类型参数调用本机函数

F#: Calling native functions with custom type parameters

我正在从 F# 调用本机 DLL。对于不带参数或仅带基本类型的函数,事实证明它非常简单,但我很难让我的代码正常工作。

C++代码为:

struct CatDef
{
    uint8_t cat;
    uint8_t arrogance;

    CatDef() : cat(0), arrogance(0) {}
};

struct HomeInfo
{
    enum { MAX_CATDEF = 5 };
    enum {
        LONE_CAT = 0x01,
    };
    CatDef  cd[MAX_CATDEF];
    uint8_t  notoriety;
    uint8_t  etc;

    HomeInfo()
        : notoriety(0), etc(0)
    {
        memset(cd, 0, sizeof(cd));
    }

    size_t howBig() const
    {
        size_t hb = 0;
        for (; hb < MAX_CATDEF; ++hb)
            if (!cd[hb].cat)
                break;
        return hb;
    }
};

struct HomeQuery
{
    char addr[ADDR_LENGTH];    // ADDR_LENGTH is 1024
    char verifiedAddr[ADDR_LENGTH];
    HomeInfo homeData;
    unsigned int id;
    Status  st;    // Status is an enum

    HomeQuery()
        : homeData()
        , id(0)
        , st(Unprocessed)
    {
        memset(addr, 0, sizeof(addr));
        memset(verifiedAddr, 0, sizeof(verifiedAddr));
    }
};

__declspec(dllexport) bool GetHomeInfo(
    HomeQuery* queries, size_t qCount, void (*callback)(HomeQuery*, size_t) = 0);

到目前为止,我有 F# 的这个:

[<Struct>]
type CatDef =
    [<DefaultValue>]
    val mutable cat : uint8
    [<DefaultValue>]
    val mutable arrogance : uint8

[<Struct>]
type HomeInfo =
    [<DefaultValue; MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)>]
    val mutable cd : CatDef array
    [<DefaultValue>]
    val mutable notoriety : uint8
    [<DefaultValue>]
    val mutable etc : uint8

[<Struct>]
type HomeQuery =
    [<DefaultValue; MarshalAs(UnmanagedType.LPStr, SizeConst = 1024)>]
    val mutable addr : string
    [<DefaultValue; MarshalAs(UnmanagedType.LPStr, SizeConst = 1024)>]
    val mutable verifiedAddr : string
    [<DefaultValue>]
    val mutable homeData : HomeInfo
    [<DefaultValue>]
    val mutable id : uint32
    [<DefaultValue>]
    val mutable st : int

type HomeQueryCallback = delegate of (HomeQuery array * uint) -> unit

module private Wrapper =
    [<DllImport(
        "Homes.dll",
        EntryPoint = "?GetHomeInfo@People@@NA_WBYUHomeQuery@1@IP6AX0I@Z@Z",
        CallingConvention = CallingConvention.Cdecl)>]
    extern bool GetHomeInfo(HomeQuery[] queries, uint qCount, HomeQueryCallback callback)

[<RequireQualifiedAccess>]
module HomeLib =
    let getHomeInfo queries = Wrapper.GetHomeInfo (queries, uint queries.Length, null)

关于类型编组的规则,我一直在参考https://docs.microsoft.com/en-us/dotnet/standard/native-interop/type-marshaling#default-rules-for-marshaling-common-types

当我调用 HomeLib.getHomeInfo 并进入本机库 (Homes.dll) 时,我看到 HomeQuery 数组中第一个(也是唯一一个)项目的字段包含乱码信息。 (qCountcallback 未损坏,并包含预期值。)如您所见,我尝试将 MarshalAs 属性添加到几个字段,但这没有任何积极影响。也没有用 [<StructLayout(LayoutKind.Sequential)>].

装饰任何类型

没关系,我已经解决了。

对于可能关心的任何人,我的 F# 代码只需进行几处更改即可工作:

  1. 我把UnmanagedType.LPStr改成了UnmanagedType.ByValTStr
  2. extern函数声明中,我用[<In; Out>]修饰了HomeQuery[] queries(即[<In; Out>]HomeQuery[] queries)。

就这些了。