【问题标题】:passing float pointer as IntPtr (pinvoke)将浮点指针作为 IntPtr (pinvoke) 传递
【发布时间】:2015-11-20 03:33:16
【问题描述】:

好的,我有一个 DLL 函数声明为:

[DllImport(mDllName, EntryPoint = "SetParam")]
public static extern bool setParam(string param_name, ref IntPtr param_value);

这个函数有很多不同的参数,例如在 C++ 中你会这样使用它。

int i=2;
setParam("width", &i);

float k=2.5f;
setParam("factor", &f);

所以我试图声明一个 C# 函数来调用这个 DLL api,我有一个用于指向整数大小写的指针:

public static void setWidth(int width)
{
    IntPtr w = new IntPtr(width);
    setParam("width", ref w);
}

但我不知道如何做第二个,我将一个指针作为 IntPtr 传递给一个浮点数。有什么想法吗?

public static void setFactor(float f)
{
    IntPtr w = new IntPtr(f); // WHAT GOES HERE??
    setParam("factor", ref w);
}

【问题讨论】:

  • 你为什么使用 IntPtr 作为 param_value。您是否尝试过使用 float 而不是 IntPtr
  • @XPD:在这里使用IntPtr 是正确的(尽管有更好的方法),因为本机setParam 本质上期望像void* 这样的东西——一个指向由其他参数定义的东西的通用指针。在 C# 方面,此类指针由 IntPtr 类表示。 IntPtr nt 的意思是“指向整数的指针”,它是“指针作为整数值”。另一件事是,rukiman 以错误的方式使用 IntPtr。 IntPtr(width)会将“width”值作为指针值,即会将width=32作为0x00000020指针
  • rukiman:请在 Bauss 的回答下查看我的 cmets。我很确定声明应该是 public static extern bool setParam(string param_name, ref IntPtr param_value); 而没有 ref。可能这就是您的new IntPtr(width) 起作用的原因,但这一切都是危险和错误的。
  • @quetzalcoatl 问题是,正确的类型是 IntPtr,而不是 ref IntPtr (IntPtr*) - rukiman 声明他正在将指针传递给指针,但他实际上是在传递指针到一个。然而,这正是非托管方所期望的,所以它确实有效 - 但它清楚地表明 rukiman 不太明白他在做什么 - 如果他真的想通过 ref int,这就是他应该声明并通过的。这只是一种非常令人困惑的方式(当然,它会在 64 位上中断)。
  • @Luaan:直到我注意到我忘记从上面的声明中删除ref 之前,我才明白你为什么要给我写这个:) 幸运的是,我提到了删除它。太糟糕的评论被锁定了,我不能再纠正它了。

标签: c# pinvoke


【解决方案1】:

如果您绝对必须使用IntPtr 并且不能只传递float 参数,那么您可以使用unsafe 编码来传递float 指针,因为IntPtrvoid 指针作为其构造函数之一中的参数。但是相应的非托管函数也必须采用void 指针。

有两种方法可以处理这个问题,传递void* 或传递IntPtr*。我会说传递 void* 可能会更好,因为您通常会对 IntPtr 做同样的事情,除了 IntPtr 将使用指针而不是函数传递。

选项 1 - IntPtr

您首先必须通过删除错误的ref 来更正 p/invoke 声明

[DllImport(mDllName, EntryPoint = "SetParam")]
public static extern bool setParam(string param_name, IntPtr param_value);

您也不要将IntPtr 传递为ref,只需传递您的IntPtr 的实例

public static unsafe void setFactor(float f)
{
        IntPtr w = new IntPtr(&f);
        setParam("factor", w);
}

或者如果你想在正文中声明不安全

public static void setFactor(float f)
{
        unsafe
        {
            IntPtr w = new IntPtr(&f);
            setParam("factor", w);
        }
}

选项 2 - void*

[DllImport(mDllName, EntryPoint = "SetParam")]
public unsafe static extern bool setParam(string param_name, void* param_value);

然后你可以设置成这样

public static unsafe void setWidth(int width)
{
    int* w = &width;
    setParam("width", w);
}

对于float

public static unsafe void setFactor(float f)
{
    float* fptr = &f;
    setParam("factor", fptr);
}

【讨论】:

  • 你为什么坚持setParam(...., REF intptr ...)? OP 提供了本机代码示例int i=2; setParam("width", &i);,其中第二个参数显然是 void*-type,所以它应该被编组为 IntPtr (~void*) 而不是 ref-IntPtr (void*&/void**)
  • OP 提供了这个ref IntPtr param_value);
  • 这是 OP 的错误之一,就像 new IntPtr(width) 一样。我的意思是,本机示例显示setParam("width", &i); 所以&i 是一个值(这里:int*)并且setParam 不能更改它(setParam 的第二个参数不能是void*&,最多可以是void*const&)。所以,通过ref 对它进行双向编组是没有意义的,指针的值不会改变。内容可能会改变,但不需要 ref。
  • 我对此投了反对票。选项1是错误的。您必须将提问者的 p/invoke 从ref IntPtr 更正为IntPtr,然后将ref w 更改为w。如果你解决了这个问题,我会删除反对票。
  • 谢谢。我删除了反对票,并为选项 1 添加了更正的 p/invoke
【解决方案2】:

除非组合太多,否则最好的方法是简单地为各种参数类型设置多个 DllImports。例如:

[DllImport(mDllName, EntryPoint = "SetParam")]
public static extern bool setParamInt32(string param_name, ref int param_value);

[DllImport(mDllName, EntryPoint = "SetParam")]
public static extern bool setParamSingle(string param_name, ref float param_value); 

然后你可以正确地调用它们

var intVal = 42;
setParamInt32("param", ref intVal);

var floatVal = 42.0f;
setParamSingle("param", ref floatVal);

在任何一种情况下使用 ref IntPtr 都是错误的——它起作用的唯一原因是在 32 位应用程序中,IntPtr 在内部是一个 32 位整数。但是,它应该是一个指针。正确的用法是这样的:

[DllImport(mDllName, EntryPoint = "SetParam")]
public static extern bool setParam(string param_name, IntPtr param_value); 

请注意,ref 不存在 - IntPtr 已经是间接寻址。

要调用它,您需要分配一些内存,并获取指向它的指针 - 或者,使用 GCHandle 直接引用托管对象:

var intValue = 42;
var handle = GCHandle.Alloc(intValue, GCHandleType.Pinned);
setParam("param", handle.AddrOfPinnedObject());

当然,请确保正确处理托管句柄 - 固定句柄对 GC 来说是一个巨大的痛苦。

手动将数据复制到非托管内存并返回也并不难:

var ptr = Marshal.AllocCoTaskMem(sizeof(int));

try
{ 
  Marshal.WriteInt32(ptr, 42);

  setParam("param", ptr);

  // If you need to read the value back:
  var result = Marshal.ReadInt32(ptr);
}
finally
{
  Marshal.FreeCoTaskMem(ptr);
}

但除非您有充分的理由不这样做,否则我只会坚持使用自动编组。

【讨论】:

  • 感谢大家的意见。我可以看到我的理解是完全错误的。这让事情变得更清楚了!
【解决方案3】:

理论上,您可以为“安全”自动编组声明不同的 DllImport 条目:

[DllImport(mDllName, EntryPoint = "SetParam")]
public static extern bool setParam(string param_name, ref int param_value);

[DllImport(mDllName, EntryPoint = "SetParam")]
public static extern bool setParam(string param_name, ref float param_value);

并像这样使用它:

public static void setFactor(float f)
{
    setParam("factor", ref f);
}

我已经为使用void * 作为签名的其他函数完成了此操作,它工作得很好,ref <value_type> 被正确地作为指针传入。

另外,ref IntPtr应该代表void **(你应该使用IntPtr代表void *,而不是ref IntPtr

如果你对 unsafe 没问题,那么你可以使用@Bauss 解决方案

此外,使用构造函数IntPtr(int) 是为指针提供一个位置,而不是该位置的值

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    • 1970-01-01
    • 1970-01-01
    • 2020-12-17
    • 2021-10-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多