2

Ok I have a DLL function declared as:

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

This function takes many different params i.e in C++ this is how you would use it.

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

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

So I am trying to declare a C# functions to call this DLL api, I have got one working for the pointer to integer case:

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

But I cannot figure out how to do the second one where I pass a pointer to a float as IntPtr. Any ideas?

public static void setFactor(float f)
{
    IntPtr w = new IntPtr(f); // WHAT GOES HERE??
    setParam("factor", ref w);
}
rukiman
  • 597
  • 10
  • 32
  • why are you using IntPtr for param_value. Did you tried with float instead of IntPtr – XPD Aug 26 '15 at 07:51
  • @XPD: Using `IntPtr` here is correct (although there are better ways), because the native `setParam` essentially expects something like `void*` - a generic pointer to something defined by other parameters. On C# side such pointers are represented by IntPtr class. `IntPtr` does nt mean "pointer to integer", it is "pointer-as-integer-value". Anothe thing is, that **rukiman** uses IntPtr in a wrong way. `IntPtr(width)` will take "width" value as the pointer value, that is, it will take width=32 as a 0x00000020 pointer – quetzalcoatl Aug 26 '15 at 08:02
  • rukiman: please see my comments under Bauss's answer. I'm pretty sure the declaration should be `public static extern bool setParam(string param_name, ref IntPtr param_value);` without `ref`. Probably that's why your `new IntPtr(width)` worked, but that was all dangerous and wrong. – quetzalcoatl Aug 26 '15 at 08:14
  • @quetzalcoatl The thing is, the proper type is `IntPtr`, not `ref IntPtr` (`IntPtr*`) - rukiman is declaring that he's passing a pointer to a pointer, but he's actually passing a pointer to a *value*. However, that's exactly what the unmanaged side expects, so it actually works - but it plainly signals that rukiman doesn't quite understand what he's doing - if he really wanted to pass a `ref int`, that's what he should declare and pass. This is just a very confusing way of doing the same (except that it breaks on 64-bit, of course). – Luaan Aug 26 '15 at 08:18
  • @Luaan: I didn't understand why are you writing this to me until I noticed I forgot to delete the `ref` from the declaration above :) Fortunatelly, I mentioned removeing it. Too bad comment's locked I can't correct it anymore. – quetzalcoatl Aug 26 '15 at 10:48

3 Answers3

5

Unless there's too many combinations, I'd say the best way is to simply have multiple DllImports for the various argument types. For example:

[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); 

You can then call them properly as

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

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

Using ref IntPtr is wrong in either case - the only reason it works at all is that in 32-bit applications, IntPtr is a 32-bit integer internally. However, it's supposed to be a pointer. A proper use would be something like this:

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

Note that the ref isn't there - IntPtr is already an indirection.

To call this, you'll need to allocate some memory, and get a pointer to that - or, use a GCHandle to refer directly to a managed object:

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

Make sure to dispose of the managed handle properly, of course - pinned handles are a huge pain for the GC.

Manually copying the data to unmanaged memory and back also isn't exactly hard:

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);
}

But I'd simply stick with automatic marshalling unless you have a very good reason not to.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • Thanks all for your input. I can see my understanding was completely wrong. This is made things a lot clearer! – rukiman Aug 27 '15 at 00:20
2

If you absolutely have to use IntPtr and can't just pass a floatparameter then you can use unsafe coding to pass a float pointer as IntPtr takes a void pointer as a parameter in one of its constructor. However the corresponding unmanaged function must also take a void pointer.

There is two ways you can deal with this, passing void* or passing IntPtr*. I would say passing void* is probably better, since you in general will be doing the same with IntPtr except for that IntPtr will be passed with the pointer instead of the function.

Option 1 - IntPtr

You first have to correct the p/invoke declaration by removing the erroneous ref

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

You do not pass IntPtr as ref either, just simply pass the instance of your IntPtr

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

Or if you want the unsafe declaration in the body

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);

Then you can set it like

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

And for the float

public static unsafe void setFactor(float f)
{
    float* fptr = &f;
    setParam("factor", fptr);
}
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Bauss
  • 2,767
  • 24
  • 28
  • why are you sticking to `setParam(...., REF intptr ...)`? OP provided native code example `int i=2; setParam("width", &i);` where the second argument obviously is void*-type, so it should be marshalled as IntPtr (~void*) not ref-IntPtr (void*&/void**) – quetzalcoatl Aug 26 '15 at 08:06
  • OP provided this `ref IntPtr param_value);` – Bauss Aug 26 '15 at 08:07
  • That was one of OP's errors, just like `new IntPtr(width)` was. I mean, the native example shows `setParam("width", &i);` so &i is a value (here: int*) and the setParam can't change it (setParam's second argument cannot be void*&, at most it could be void*const&). So, marshalling it two-ways through `ref` has no point, the value of the value of the pointer will not change. Contents may change, but ref is not needed for that. – quetzalcoatl Aug 26 '15 at 08:07
  • 1
    I downvoted this. Option 1 is wrong. You have to correct the asker's p/invoke from `ref IntPtr` to `IntPtr`, and then change `ref w` to `w`. If you fix that I'll remove the downvote. – David Heffernan Aug 26 '15 at 08:49
  • Removed `ref` and corrected OP about the `ref IntPtr` – Bauss Aug 26 '15 at 09:47
  • 1
    Thanks. I removed downvote, and also added a corrected p/invoke for option 1 – David Heffernan Aug 26 '15 at 10:07
1

You can theoretically declare different DllImport entries for "safe" automatic marshalling:

[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);

And use it like:

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

I have done this for other functions using void * as a signature and it works just fine, the ref <value_type> gets correctly passed in as a pointer.

Also, ref IntPtr should be for void ** (you'd use IntPtr for void *, not ref IntPtr)

If you are ok with unsafe, then you can use @Bauss solution

Furthermore, using the constructor IntPtr(int) is giving a location for the pointer, not the value at the location

Jcl
  • 27,696
  • 5
  • 61
  • 92