C# 将指向结构(包含不可直接传送类型)的指针传递给非托管 C++ DLL

标签 c# pointers dll struct

我尝试在 C# 代码中使用 C++ DLL。
所有读取的数据都必须作为指向结构的指针传递给 dll。我的第一个想法是保留一些非托管内存,将指针传递给函数,然后提取数据。问题是这些函数只会返回一个错误代码,翻译为“参数无效”。

来自 DLL header (结果和 BusPortId 已缩短)

typedef enum {
    BUS_COM1 = 0x01,  // 2 to 15 ommited
    BUS_COM16 = 0x10,
    BUS_USB1 = 0x101,  // 2 to 15 ommited
    BUS_USB16 = 0x110
} BusPortId;

typedef enum {
    BUS_SUCCESS    = 0,     //!< The operation was completed successfully
    BUS_ERROR      = 0x100,  //!< An error occured
    BUS_INVALIDARG = 0x1000, //!< An argument is not valid
} Result


struct BusPortInfo
{
    ULONG       portInfoSize;
    CHAR        portText[64];
    BOOL        portLocked;
    BusPortId   portId;
};

Result BUSDRV_API busGetAvailablePortCount( ULONG *retCount );

Result BUSDRV_API busGetAvailablePort( ULONG index, BusPortInfo *portInfo );

到目前为止我的 C# 程序的相关部分

enum BusPortId
{
    BUS_COM1 = 0x01,  // 2 to 15 ommited
    BUS_COM16 = 0x10,
    BUS_USB1 = 0x101,  // 2 to 15 ommited
    BUS_USB16 = 0x110
};

public enum Result
{
    BUS_SUCCESS = 0,       //!< The operation was completed successfully
    BUS_ERROR = 0x100,  //!< An error occured
    BUS_INVALIDARG = 0x1000, //!< An argument is not valid
};

struct BusPortInfo
{
    public ULONG portInfoSize;
    unsafe public fixed char portText[64];
    public BOOL portLocked;
    public BusPortId portId;
}

[DllImport(DLL_Path)]
unsafe static extern Result busGetAvailablePortCount(ULONG* retCount);
[DllImport(DLL_Path)]
unsafe static extern Result busGetAvailablePort(ULONG index, BusPortInfo* portInfo);

ulong count= 0;
Result res = busGetAvailablePortCount(&count);

ulong index = 0;
BusPortInfo info = new BusPortInfo();
Result res = busGetAvailablePort(0, &info);

对busGetAvailablePortCount(和其他类似函数)的调用没有任何问题。但是当我调用busGetAvailablePort时,我在控制台输出中得到以下内容

无法编码(marshal)“参数#2”:指针无法引用编码(marshal)结构。请改用 ByRef。

问题是我可以在 C# 中更改结构,以便可以传递指针,但 DLL 中的函数也会返回“参数无效”

我必须对我的结构做什么,才能将指向它的指针传递给函数,同时仍然被 DLL 接受?

附注抱歉英语不好,我不是母语人士。

最佳答案

这些声明存在很多问题,当程序员不断修改代码以尝试使 pinvoke 调用工作时,往往会发生这些问题。最可能正确的结构声明是:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    struct BusPortInfo {
        public int portInfoSize;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string portText;
        public bool portLocked;
        public BusPortId portId;
    }

强调 native 代码中的 ULONG 是 32 位类型,而 fixed 是没有必要的,而且非常尴尬。由于 bool 和 string 成员,该结构不可 blittable,无需担心。

[DllImport] 声明需要正确声明第二个参数。 CallingConvention 属性始终很重要,但我们看不到 BUSDRV_API 的含义。弃踢:

    [DllImport(DLL_Path, CallingConvention = CallingConvention.StdCall)]
    static extern Result busGetAvailablePortCount(out int retCount );

    [DllImport(DLL_Path, CallingConvention = CallingConvention.StdCall)]
    static extern Result busGetAvailablePort(int index, 
                            [In, Out] ref BusPortInfo portInfo);

并且调用看起来不正确。当结构体具有“size”成员时,api 合约通常要求在调用之前设置它。这是一项安全措施,它确保当调用者使用错误的结构声明时 api 不会损坏内存。当设置错误时,预期结果是“无效参数”错误。所以写得类似于:

int count;
Result res = busGetAvailablePortCount(out count);
if (res != Result.BUS_SUCCESS) throw new Exception("Api failure " + res.ToString());

for (int ix = 0; ix < count; ++ix) {
    BusPortInfo info;
    info.portInfoSize = Marshal.SizeOf(typeof(BusPortInfo));   // Important!
    res = busGetAvailablePort(ix, ref info);
    if (res != Result.BUS_SUCCESS) throw new Exception("Api failure " + res.ToString());
    // etc...
}

当然没有经过测试,应该在大概范围内。如果仍然存在问题,请验证 native 代码中的 sizeof(BusPortInfo) 是否与 C# 中的 Marshal.SizeOf(typeof(BusPortInfo)) 的值匹配。如果全部失败,则改用 C++/CLI,这样您就可以直接使用 native 声明。并与 DLL 的所有者联系以获取正确的使用说明,最好他会为您编写一个 pinvoke 示例。

关于C# 将指向结构(包含不可直接传送类型)的指针传递给非托管 C++ DLL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42201106/

相关文章:

delphi - 使用 BPL 加载 Delphi 对象运行时

c# - 从多线程单元访问带有线程模型单元的 COM dll 时出错

c++ - 指向静态分配对象的指针

c - 它不是随机的,而是生成数字序列

c++ - 声明一个二维指针对象数组

.net - Dllimport 可在控制台应用程序中运行,但不能在 ASP.NET 网站中运行

c# - 在 Linq 中按问题分组

c# - 自定义标签到 PDF

c# - 在 SSIS 中通过 C# 脚本向表添加派生列

c++ - 在同一个 DLL 中使用 _COM_SMARTPTR CreateInstance 而无需注册