Sign in to follow this  
hypa_dude

[.net] using "fixed" in unsafe c#

Recommended Posts

I have tried compiling the following code and get a compiler error.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
    unsafe class ALPapi2
    {
        public static long ALP_DEFAULT = 0L;
        public static long deviceid = 0;
        public static long returnvalue = 0;

        [DllImport("alp3S.dll", CallingConvention = CallingConvention.ThisCall)]
        public static extern long AlpDevAlloc(long DeviceNum, long InitFlag, long* DeviceIdPtr);
        
        public static void initialize()
        {
            returnvalue = AlpDevAlloc(ALP_DEFAULT, ALP_DEFAULT, &deviceid);
        }
    }
}


I get compiler error CS0212: "You can only take the address of an unfixed expression inside of a fixed statement initializer". After looking at the MSDN article, I can not figure out how to implement a fixed statement in this context. If there is another way to do this, I'd love to hear it. Any help would be very appreciated! The MSDN article can be found here.

Share this post


Link to post
Share on other sites
You need a fixed pointer to deviceid, to guarantee that the GC won't move it around in memory while you're calling the P/Invoke. To do that, you can do something like this:


public static void initialize()
{
fixed (long* p = &deviceid)
{
returnvalue = AlpDevAlloc(ALP_DEFAULT, ALP_DEFAULT, p);
}
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Codeka
You need a fixed pointer to deviceid, to guarantee that the GC won't move it around in memory while you're calling the P/Invoke. To do that, you can do something like this:

*** Source Snippet Removed ***

Note that while this "does" work, if that pointer is stored and used elsewhere in the unmanaged portion of the application at a later point in time it will result in problems (crashes, etc). Also, you don't NEED to do that at all for an output only parameter, just call it out, assuming that's all it does.


[DllImport("alp3S.dll", CallingConvention = CallingConvention.ThisCall)]
public static extern long AlpDevAlloc(long deviceNum, long initFlag, out IntPtr deviceIdPtr);
...
IntPtr p;
AlpDevAlloc(devNum, initFlags, out p);



Avoid using fixed as much as possible, it can introduce very large amounts of overhead, along with possible fragmentation of the managed heap.

Share this post


Link to post
Share on other sites
Hello,

Thanks for the helpful responses! I suppose I should have provided a little more information about the API I'm using.

The API is provided as a means of communicating with a DMD (a DLP projector component). The API is written in CPP and for some reason is full of pointers. In the documentation, the following is given:

Quote:

Format:
long AlpDevAlloc(long DeviceNum, long InitFlag, ALP_ID* DeviceIdPtr)

Description:
This function allocates and ALP hardware system (board set) and returns an ALP handle so that it can be used by subsequence API functions. An error is reported if the requested device is not available or not ready.


In the associated header file, there is a

typedef unsigned long ALP_ID;

So I simply did the P/Invoke with DeviceIdPtr as a long. In other methods, the variable that DeviceIdPtr points to is called. For example:

Quote:


Format:
long AlpDevInquire(ALP_ID DeviceId, long InquireType, long *UserVarPtr)

Description: This function inquires about a specified ALP device parameter setting.



What is the most stable way of calling AlpDevAlloc, and then later using the value of DeviceId?

Again, thanks for your help!

[Edited by - hypa_dude on March 18, 2009 12:37:09 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by kanato
You should be careful with longs.. in C++ a long is typically 32-bits but in C# it's 64 bits.


Unless you are on a 64-bit system, where a long can either be 64-bits or 32-bits (depending on the compiler).

As a rule of thumb, long is (32-bits / 64-bits architecture):
Linux, Mac OS X -> 32 / 64 -> IntPtr in C#
Windows -> 32 / 32 -> int in C#

"Long" in C# maps to "long long" in C++.

Share this post


Link to post
Share on other sites
Quote:
Original post by hypa_dude
Hello,

Thanks for the helpful responses! I suppose I should have provided a little more information about the API I'm using.

The API is provided as a means of communicating with a DMD (a DLP projector component). The API is written in CPP and for some reason is full of pointers. In the documentation, the following is given:

Quote:

Format:
long AlpDevAlloc(long DeviceNum, long InitFlag, ALP_ID* DeviceIdPtr)

Description:
This function allocates and ALP hardware system (board set) and returns an ALP handle so that it can be used by subsequence API functions. An error is reported if the requested device is not available or not ready.


In the associated header file, there is a

typedef unsigned long ALP_ID;

So I simply did the P/Invoke with DeviceIdPtr as a long. In other methods, the variable that DeviceIdPtr points to is called. For example:

Quote:


Format:
long AlpDevInquire(ALP_ID DeviceId, long InquireType, long *UserVarPtr)

Description: This function inquires about a specified ALP device parameter setting.



What is the most stable way of calling AlpDevAlloc, and then later using the value of DeviceId?

Again, thanks for your help!

It's operating on the same principle as OpenGL, where ALP_ID is a handle. As such you should be using an out parameter and an IntPtr, as demonstrated in my previous post. Also, you should identify what they mean by "long", although in the case of VS and GCC a long is generally 32 bits even in 64 bit mode, with long long being 64 bits. If the previous statement is true, then you should be using int, not long.

Share this post


Link to post
Share on other sites
Again, thanks for the input! I've worked my way through most of these problems, but the following code is still not working properly.



class ALPapi
{
...
[DllImport("alp3.dll")]
public static extern int AlpSeqPut(IntPtr DeviceId, IntPtr SequenceId, int PicOffset, int PicLoad, out byte[,,] UserArrayPtr);
...
}


public partial class Form1 : Form
{
...
ALPapi.AlpSeqPut(ALPapi.devId, ALPapi.seqId, 0, numberOfBitmaps, out loadedArray);
...
}




Where loadedArray is a byte[5,768,1024] I created by converting a series of black/white bitmaps into a 3-dimensional byte array (BitDepth = 1) . all long types in the API documentation have been replaced with int types. The code above compiles and runs, but AlpSeqPut returns a code that indicates an invalid parameter.

I know there can be problems with Marshalling arrays. Also, I'm still not 100% confident of my use of "out" in this context. So if these are the problem, I'd like to know here before I contact the developers of the API. Below is an excerpt from the documentation.

Quote:

From API documentation:
long AlpSeqPut(ALP_ID DeviceId, ALP_ID SequenceId, long PicOffset,long PicLoad, void *UserArrayPtr)

UserArrayPtr data formats depends on bit depth specified in AlpSeqAlloc.
Bitplanes = 1...8: 1 Byte (char unsigned)
BitPlanes = 9...16: 2 Byte (short)

The user provided data must be aligned at the HIGHEST bit positions.
Example1:
Bitplanes = 6
1 1 1 1 1 1 0 0
5 4 3 2 1 0 x x

Share this post


Link to post
Share on other sites
Reading the documentation, I get the feeling that UserArrayPtr is an "in" parameter, not "out".

I am not familiar with the API, but who is responsible for allocating memory here? If it is the user, try this:


IntPtr AlpSeqPut(IntPtr DeviceId, IntPtr SequenceId, IntPtr PicOffset, IntPtr PicLoad, IntPtr UserArrayPtr);

byte[] data = new byte[5,768,1024];
unsafe
{
fixed (byte* data_ptr = data)
{
IntPtr result = AlpSeqPut(device_id, sequence_id, IntPtr.Zero, new IntPtr(numberOfBitmaps), new IntPtr(data_ptr));
}
}



Edit: Mapped long parameters to IntPtr here. You'll have to check with the documentation to see whether they should be 32bits or 64bits on 64bit platforms (use int in the first case, IntPtr in the latter).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this