如何处理空或可选的DLL结构参数?

21

如何在从C#调用pinvoke的dll方法中处理可选的struct参数?例如,这里的lpSecurityAttributes参数应该在缺席时传递null

传递struct的正确方式似乎是使用ref,但它不能有可选参数或通常接受null

有哪些方法可以实现这一点?

1个回答

22

您有几个选择

1) 使用class而不是struct

我认为这种方法最简单。只需将struct声明为class即可:

[StructLayout(LayoutKind.Sequential)]
public class CStruct
{
    //member-list
}

然后声明你的方法:

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static extern int DLLFunction(CStruct cStruct, ...);

如果您的可选参数恰好是最后一个参数,则可以使用 CStruct cStruct = null 作为该参数,而不是显式地传递 null,这样可以将其排除在外。您也可以编写一个包装方法来使用此方法,并确保可选参数位于最后。

2) 使用 IntPtrIntPtr.Zero

使用一个 struct:

[StructLayout(LayoutKind.Sequential)]
public struct CStruct
{
    //member-list
}

并声明你的方法为:

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static extern int DLLFunction(IntPtr cStruct, ...);

在非 null 情况下,将该结构体封送到指针并调用方法:

IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CStruct)));
try{
    Marshal.StructureToPtr(myCStruct, ptr, false);
    DLLFunction(ptr, ...);
} finally {
    Marshal.FreeHGlobal(ptr);
}

null 的情况下,使用 IntPtr.Zero 调用该方法:

DLLFunction(IntPtr.Zero, ...);

如果这个参数恰好是列表中的最后一个(或者你使用封装器来实现),则可以将此参数变为可选。方法是将 IntPtr cStruct = default(IntPtr) 用作参数。(由于 default(IntPtr) 创建了 IntPtr.Zero)。

3) 重载方法以避免封送

请像第2步中所述,使用 struct

只需为非 null 情况声明一个选项:

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static extern int DLLFunction(ref cStruct, ...);

另一个是针对null情况的:

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static extern int DLLFunction(IntPtr cStruct, ...);

当传递一个 struct 时,第一种方法会自动调用;当传递 IntPtr.Zero 时,第二种方法会被调用。如果在声明 IntPtr 版本时使用了可选参数(如上方2)的底部所示),当排除 cStruct 参数时,它将自动调用它。

4) 使用 unsafe 的原始指针

2)中一样使用结构体,并声明您的方法(注意使用unsafe关键字):

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static unsafe extern int DLLFunction(CStruct* cStruct, ...);
在非null情况下,你要传递&myCStruct,而在null情况下只需传递null。就像1)中所述,如果这个可选参数是最后一个,则可以将该参数声明为CStruct* cStruct = null,以在排除cStruct时自动传递null感谢@dialer提出了这种方法。

1
你也可以(我的个人喜好是)使用不安全指针声明P/Invoke签名。static unsafe extern int DLLFunction(TheStruct* struct, ...); 这有几个优点。你可以使用实际值类型而不是引用类型(如果堆栈分配性能很重要),可以传递 null,不需要另一个重载,没有编组(实际上强制它是可平铺的,这反过来又增加了性能),并且它是类型安全的(不像 IntPtr)。显然的缺点是你必须使用 unsafe(尽管公平地说,使用 IntPtr 也不会更安全)。 - dialer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接