将浮点指针作为 IntPtr 传递(pinvoke)
passing float pointer as IntPtr (pinvoke)
好的,我有一个 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
而不能只传递 float
参数,那么您可以使用 unsafe
编码将 float
指针作为 IntPtr
将 void
指针作为其构造函数之一的参数。但是相应的非托管函数也必须采用 void
指针。
有两种方法可以解决这个问题,传递 void*
或传递 IntPtr*
。我会说传递 void*
可能更好,因为您通常会对 IntPtr
做同样的事情,除了 IntPtr
将通过指针而不是函数传递。
Option 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);
}
}
Option 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);
}
理论上您可以为 "safe" 自动编组声明不同的 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
)
如果你对不安全没问题,那么你可以使用@Bauss 解决方案
此外,使用构造函数 IntPtr(int)
是为指针提供一个位置,而不是该位置的值
除非组合太多,否则最好的方法是简单地为各种参数类型设置多个 DllImport
。例如:
[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);
}
但除非您有充分的理由不这样做,否则我会坚持使用自动编组。
好的,我有一个 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
而不能只传递 float
参数,那么您可以使用 unsafe
编码将 float
指针作为 IntPtr
将 void
指针作为其构造函数之一的参数。但是相应的非托管函数也必须采用 void
指针。
有两种方法可以解决这个问题,传递 void*
或传递 IntPtr*
。我会说传递 void*
可能更好,因为您通常会对 IntPtr
做同样的事情,除了 IntPtr
将通过指针而不是函数传递。
Option 1 -
IntPtr
您首先必须通过删除错误的 ref
[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);
}
}
Option 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);
}
理论上您可以为 "safe" 自动编组声明不同的 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
)
如果你对不安全没问题,那么你可以使用@Bauss 解决方案
此外,使用构造函数 IntPtr(int)
是为指针提供一个位置,而不是该位置的值
除非组合太多,否则最好的方法是简单地为各种参数类型设置多个 DllImport
。例如:
[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);
}
但除非您有充分的理由不这样做,否则我会坚持使用自动编组。