Please Help - P/Invoke and unmanaged types

  • Thread starter Thread starter Don
  • Start date Start date
D

Don

I have a third party C++ DLL that I am trying to use from C#. The
specific function I am trying to use is declared in C++ as follows:

ladybugConvertToMultipleBGRU32(
LadybugContext context,
const LadybugImage* pimage,
unsigned char* arpszDestBuffers[ LADYBUG_NUM_CAMERAS ],
LadybugImageInfo* pImageInfo );


It is used, in C++, as follows (some code chopped for brevety):

unsigned char* arpTextureBuffers[ LADYBUG_NUM_CAMERAS ];
for(i = 0; i<LADYBUG_NUM_CAMERAS;i++)
{
arpTextureBuffers =
new unsigned char[LADYBUG_RAW_IMAGE_COLS * LADYBUG_RAW_IMAGE_ROWS *
4];
}

error = ladybugConvertToMultipleBGRU32(
ladybug,
&image,
&arpTextureBuffers[0],
NULL);


I have tried (unsuccessfully, for the past two days) many combinations
of DllImport function declarations and ways of constructing the item
to pass in the third parameter (&arpTextureBuffers[0]). I have seen a
zillion posts, but none have been close enough to help me. How do I
construct the DllImport function, and how to I create the appropriate
item to pass to the third parameter? Any help would be greatly
appreciated.

Thanks in advance,
Don
 
Hi,

You can't have a managed type that you can pass as such.

1. You can use a managed type and an unmanaged buffer and copy between them
after and/or before the function call.

* managed buffer:
byte [][] mBuffer = new byte[LB_NUM_CAMERAS][];
for (int i=0; i<LB_NUM_CAMERAS; ++i)
mBuffer = new byte[ LB_COLS * LB_ROWS * 4 ];

* unmanaged buffer:
IntPtr [] pBuffer = new IntPtr[6];
for (int i=0; i<LB_NUM_CAMERAS; ++i)
pBuffer = Marshal.AllocHGlobal( LB_COLS * LB_ROWS * 4 );

* copy managed to unmanaged (eg before function call)
for (int i=0; i<LB_NUM_CAMERAS; ++i)
Marshal.Copy ( mBuffer, 0, pBuffer, LB_COLS * LB_ROWS * 4 );

* copy unmanaged to managed (eg after function call)
for (int i=0; i<LB_NUM_CAMERAS; ++i)
Marshal.Copy ( pBuffer, mBuffer, 0, LB_COLS * LB_ROWS * 4 );

* unmanaged function :

[DllImport(....) ]
ladybugConvertToMultipleBGRU32( ..., ... , IntPtr [] p, ... );

pBuffer is an IntPtr[] so pass it as the 3th parameter.

* cleanup unmanaged memory
for (int i= 0; i<LB_NUM_CAMERAS; ++i )
Marshal.FreeHGlobal ( pBuffer );


2. _OR_ you can only use an unmanaged buffer and manipulate this with
properties or methods. This way you avoid copying. (You could put this in
a class and make sure there is access to pBuffer so you can call the
unmanaged function)

* unmanaged buffer:
IntPtr [] pBuffer = new IntPtr[6];
for (int i=0; i<LB_NUM_CAMERAS; ++i)
pBuffer = Marshal.AllocHGlobal( LB_COLS * LB_ROWS * 4 );

* read data
public byte ReadData( int camera, int idx )
{
// TODO check if idx < rows*cols*4
return Marshal.ReadByte ( pBuffer[camera], idx );
}

* write data
public void WriteData( int camera, int idx, byte value )
{
// TODO check if idx < rows*cols*4
Marshal.WriteByte ( pBuffer[camera], idx, value );
}



HTH,
greetings


Don said:
I have a third party C++ DLL that I am trying to use from C#. The
specific function I am trying to use is declared in C++ as follows:

ladybugConvertToMultipleBGRU32(
LadybugContext context,
const LadybugImage* pimage,
unsigned char* arpszDestBuffers[ LADYBUG_NUM_CAMERAS ],
LadybugImageInfo* pImageInfo );


It is used, in C++, as follows (some code chopped for brevety):

unsigned char* arpTextureBuffers[ LADYBUG_NUM_CAMERAS ];
for(i = 0; i<LADYBUG_NUM_CAMERAS;i++)
{
arpTextureBuffers =
new unsigned char[LADYBUG_RAW_IMAGE_COLS * LADYBUG_RAW_IMAGE_ROWS *
4];
}

error = ladybugConvertToMultipleBGRU32(
ladybug,
&image,
&arpTextureBuffers[0],
NULL);


I have tried (unsuccessfully, for the past two days) many combinations
of DllImport function declarations and ways of constructing the item
to pass in the third parameter (&arpTextureBuffers[0]). I have seen a
zillion posts, but none have been close enough to help me. How do I
construct the DllImport function, and how to I create the appropriate
item to pass to the third parameter? Any help would be greatly
appreciated.

Thanks in advance,
Don
 
Hi,

Thanks for your reply. I am still having some trouble, probably with
the 2nd parameter (LadybugImage* pimage). I declared the LadyBugImage
struct as a class in my C# code as follows:

[StructLayout(LayoutKind.Sequential)]
unsafe public class LadybugImage{
public UInt32 uiCols = 0;
public UInt32 uiRows = 0;
public LadybugVideoMode videoMode = new LadybugVideoMode();
public LadybugTimestamp timestamp = new LadybugTimestamp();
public LadybugImageInfo imageInfo = new LadybugImageInfo();
// public string pData = "";
unsafe public byte* pData;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
public UInt32[] ulReserved;
}

I suspect that the member pData is causing me grief, and/or the fact
that I used class instead of struct. Any suggestions here? If it
weren't for the complex structures, I think this would be pretty easy.

I couldn't help but notice that you substituted a 6 for LB_NUM_CAMERAS
when declaring pBuffer. Are you familiar with the LadyBug API? If so
are you using it w/ .NET?

Thanks again,
Don


BMermuys said:
Hi,

You can't have a managed type that you can pass as such.

1. You can use a managed type and an unmanaged buffer and copy between them
after and/or before the function call.

* managed buffer:
byte [][] mBuffer = new byte[LB_NUM_CAMERAS][];
for (int i=0; i<LB_NUM_CAMERAS; ++i)
mBuffer = new byte[ LB_COLS * LB_ROWS * 4 ];

* unmanaged buffer:
IntPtr [] pBuffer = new IntPtr[6];
for (int i=0; i<LB_NUM_CAMERAS; ++i)
pBuffer = Marshal.AllocHGlobal( LB_COLS * LB_ROWS * 4 );

* copy managed to unmanaged (eg before function call)
for (int i=0; i<LB_NUM_CAMERAS; ++i)
Marshal.Copy ( mBuffer, 0, pBuffer, LB_COLS * LB_ROWS * 4 );

* copy unmanaged to managed (eg after function call)
for (int i=0; i<LB_NUM_CAMERAS; ++i)
Marshal.Copy ( pBuffer, mBuffer, 0, LB_COLS * LB_ROWS * 4 );

* unmanaged function :

[DllImport(....) ]
ladybugConvertToMultipleBGRU32( ..., ... , IntPtr [] p, ... );

pBuffer is an IntPtr[] so pass it as the 3th parameter.

* cleanup unmanaged memory
for (int i= 0; i<LB_NUM_CAMERAS; ++i )
Marshal.FreeHGlobal ( pBuffer );


2. _OR_ you can only use an unmanaged buffer and manipulate this with
properties or methods. This way you avoid copying. (You could put this in
a class and make sure there is access to pBuffer so you can call the
unmanaged function)

* unmanaged buffer:
IntPtr [] pBuffer = new IntPtr[6];
for (int i=0; i<LB_NUM_CAMERAS; ++i)
pBuffer = Marshal.AllocHGlobal( LB_COLS * LB_ROWS * 4 );

* read data
public byte ReadData( int camera, int idx )
{
// TODO check if idx < rows*cols*4
return Marshal.ReadByte ( pBuffer[camera], idx );
}

* write data
public void WriteData( int camera, int idx, byte value )
{
// TODO check if idx < rows*cols*4
Marshal.WriteByte ( pBuffer[camera], idx, value );
}



HTH,
greetings


Don said:
I have a third party C++ DLL that I am trying to use from C#. The
specific function I am trying to use is declared in C++ as follows:

ladybugConvertToMultipleBGRU32(
LadybugContext context,
const LadybugImage* pimage,
unsigned char* arpszDestBuffers[ LADYBUG_NUM_CAMERAS ],
LadybugImageInfo* pImageInfo );


It is used, in C++, as follows (some code chopped for brevety):

unsigned char* arpTextureBuffers[ LADYBUG_NUM_CAMERAS ];
for(i = 0; i<LADYBUG_NUM_CAMERAS;i++)
{
arpTextureBuffers =
new unsigned char[LADYBUG_RAW_IMAGE_COLS * LADYBUG_RAW_IMAGE_ROWS *
4];
}

error = ladybugConvertToMultipleBGRU32(
ladybug,
&image,
&arpTextureBuffers[0],
NULL);


I have tried (unsuccessfully, for the past two days) many combinations
of DllImport function declarations and ways of constructing the item
to pass in the third parameter (&arpTextureBuffers[0]). I have seen a
zillion posts, but none have been close enough to help me. How do I
construct the DllImport function, and how to I create the appropriate
item to pass to the third parameter? Any help would be greatly
appreciated.

Thanks in advance,
Don
 
Hi,

Don said:
Hi,

Thanks for your reply. I am still having some trouble, probably with
the 2nd parameter (LadybugImage* pimage). I declared the LadyBugImage
struct as a class in my C# code as follows:

[StructLayout(LayoutKind.Sequential)]
unsafe public class LadybugImage{
public UInt32 uiCols = 0;
public UInt32 uiRows = 0;
public LadybugVideoMode videoMode = new LadybugVideoMode();
public LadybugTimestamp timestamp = new LadybugTimestamp();
public LadybugImageInfo imageInfo = new LadybugImageInfo();
// public string pData = "";
unsafe public byte* pData;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
public UInt32[] ulReserved;
}

I suspect that the member pData is causing me grief, and/or the fact
that I used class instead of struct. Any suggestions here? If it

I can only say that if the parameter is LadybugImage* then you can use a
class or a struct as ref parameter). Both are valid.
weren't for the complex structures, I think this would be pretty easy.

I couldn't help but notice that you substituted a 6 for LB_NUM_CAMERAS
when declaring pBuffer. Are you familiar with the LadyBug API? If so

Not at all. I just read your previous post. I did check the internet, found
the ladybug but no public information about the sdk.

If you want more help you should post all c structs (concerning the
function). Also some information about what pData suppose to do. Do you
need to fill it, does the dll fill it or does the dll just set a pointer to
allocated data.
Maybe some c code that demonstrates the complete use of the function, etc...

hth,
greetings
are you using it w/ .NET?

Thanks again,
Don


"BMermuys" <[email protected]> wrote in message
Hi,

You can't have a managed type that you can pass as such.

1. You can use a managed type and an unmanaged buffer and copy between them
after and/or before the function call.

* managed buffer:
byte [][] mBuffer = new byte[LB_NUM_CAMERAS][];
for (int i=0; i<LB_NUM_CAMERAS; ++i)
mBuffer = new byte[ LB_COLS * LB_ROWS * 4 ];

* unmanaged buffer:
IntPtr [] pBuffer = new IntPtr[6];
for (int i=0; i<LB_NUM_CAMERAS; ++i)
pBuffer = Marshal.AllocHGlobal( LB_COLS * LB_ROWS * 4 );

* copy managed to unmanaged (eg before function call)
for (int i=0; i<LB_NUM_CAMERAS; ++i)
Marshal.Copy ( mBuffer, 0, pBuffer, LB_COLS * LB_ROWS * 4 );

* copy unmanaged to managed (eg after function call)
for (int i=0; i<LB_NUM_CAMERAS; ++i)
Marshal.Copy ( pBuffer, mBuffer, 0, LB_COLS * LB_ROWS * 4 );

* unmanaged function :

[DllImport(....) ]
ladybugConvertToMultipleBGRU32( ..., ... , IntPtr [] p, ... );

pBuffer is an IntPtr[] so pass it as the 3th parameter.

* cleanup unmanaged memory
for (int i= 0; i<LB_NUM_CAMERAS; ++i )
Marshal.FreeHGlobal ( pBuffer );


2. _OR_ you can only use an unmanaged buffer and manipulate this with
properties or methods. This way you avoid copying. (You could put this in
a class and make sure there is access to pBuffer so you can call the
unmanaged function)

* unmanaged buffer:
IntPtr [] pBuffer = new IntPtr[6];
for (int i=0; i<LB_NUM_CAMERAS; ++i)
pBuffer = Marshal.AllocHGlobal( LB_COLS * LB_ROWS * 4 );

* read data
public byte ReadData( int camera, int idx )
{
// TODO check if idx < rows*cols*4
return Marshal.ReadByte ( pBuffer[camera], idx );
}

* write data
public void WriteData( int camera, int idx, byte value )
{
// TODO check if idx < rows*cols*4
Marshal.WriteByte ( pBuffer[camera], idx, value );
}



HTH,
greetings


Don said:
I have a third party C++ DLL that I am trying to use from C#. The
specific function I am trying to use is declared in C++ as follows:

ladybugConvertToMultipleBGRU32(
LadybugContext context,
const LadybugImage* pimage,
unsigned char* arpszDestBuffers[ LADYBUG_NUM_CAMERAS ],
LadybugImageInfo* pImageInfo );


It is used, in C++, as follows (some code chopped for brevety):

unsigned char* arpTextureBuffers[ LADYBUG_NUM_CAMERAS ];
for(i = 0; i<LADYBUG_NUM_CAMERAS;i++)
{
arpTextureBuffers =
new unsigned char[LADYBUG_RAW_IMAGE_COLS * LADYBUG_RAW_IMAGE_ROWS *
4];
}

error = ladybugConvertToMultipleBGRU32(
ladybug,
&image,
&arpTextureBuffers[0],
NULL);


I have tried (unsuccessfully, for the past two days) many combinations
of DllImport function declarations and ways of constructing the item
to pass in the third parameter (&arpTextureBuffers[0]). I have seen a
zillion posts, but none have been close enough to help me. How do I
construct the DllImport function, and how to I create the appropriate
item to pass to the third parameter? Any help would be greatly
appreciated.

Thanks in advance,
Don
 
Hi again,

Thanks for the response. The relavent C++ typedefs are as follows:

typedef struct LadybugImage
{
/** Columns, in pixels, of a single image corresponding to the
current
video mode. */
unsigned int uiCols;
/** Rows, in pixels, of a single image corresponding to the current
video mode. */
unsigned int uiRows;
/** Video mode that this image was captured with. */
LadybugVideoMode videoMode;
/** Timestamp of this image. */
LadybugTimestamp timeStamp;
/** Image information for this image. */
LadybugImageInfo imageInfo;
/**
* Pointer to the actual image data. The data will be of length
* iRows * iCols * LADYBUG_NUM_CAMERAS. The format of this data
* is byte interleaved by camera and not useful for anything
besides
* feeding into one of the colour processing functions, or storage
* on disk for later processing.
*/
unsigned char* pData;
/** Reserved for future image information */
unsigned long ulReserved[ 4 ];

} LadybugImage;

/**
* The possible ladybug preview video modes. Modes other than
1024x768 do
* hardware downsampling to provide higher framerates. These do not
affect
* the size of the images saved to the HD.
*/
typedef enum LadybugVideoMode
{
/** 1024 x 768 x 6 cameras */
LADYBUG_VIDEOMODE_LADYBUG_1024x768x6,
/** 512 x 384 x 6 cameras */
LADYBUG_VIDEOMODE_LADYBUG_512x384x6,
/** 256 x 192 x 6 cameras */
LADYBUG_VIDEOMODE_LADYBUG_256x192x6,
/** 128 x 96 x 6 cameras */
LADYBUG_VIDEOMODE_LADYBUG_128x96x6,
/** Number of possible video modes. */
LADYBUG_NUM_VIDEOMODES,
/** Hook for "any usable video mode." */
LADYBUG_VIDEOMODE_ANY,

} LadybugVideoMode;

/**
* The timestamp structure for ladybug images. "Cycle time" in this
context
* is a low-level 1394 construct used for synchronizing cameras across
* multiple nodes.
*/
typedef struct LadybugTimestamp
{
/** The number of seconds since the epoch. */
unsigned long ulSeconds;
/** The microseconds component. */
unsigned long ulMicroSeconds;
/** The cycle time seconds. 0-127. */
unsigned long ulCycleSeconds;
/** The cycle time count. 0-7999. */
unsigned long ulCycleCount;

} LadybugTimestamp;

/**
* Out of band image information stored to the hard drive as part of
each
* image set at capture time.
*/
typedef struct LadybugImageInfo
{
/** Constant fingerprint, should be 0xCAFEBABE */
unsigned long ulFingerprint;
/** Structure version number, should be 0x00000002 */
unsigned long ulVersion;
/** Timestamp, in seconds, since the UNIX time epoch. */
unsigned long ulTimeSeconds;
/** Microsecond fraction of above second */
unsigned long ulTimeMicroSeconds;
/** Sequence number of the image. Reset to zero when the head
powers up
* and incremented for every image. */
unsigned long ulSequenceId;
/** Horizontal refresh rate. (reserved) */
unsigned long ulHRate;
/** Actual adjusted gains used by each of the 6 cameras. Similar
to the
* DCAM gain register. */
unsigned long arulGainAdjust[ 6 ];
/** A copy of the DCAM whitebalance register */
unsigned long ulWhiteBalance;
/** This is the same as register 0x1044, described in the PGR
Dragonfly
* Technical Reference Manual. */
unsigned long ulBayerGain;
/** This is the same as register 0x1040, described in the PGR
Dragonfly
* Technical Reference Manual. */
unsigned long ulBayerMap;
/** A copy of the Brightness DCAM register. */
unsigned long ulBrightness;
/** A copy of the Gamma DCAM register. */
unsigned long ulGamma;
/** The head unit serial number. */
unsigned long ulSerialNum;
/** Shutter values for each camera. Similar to the DCAM shutter
* register. */
unsigned long ulShutter[ 6 ];
/** Reserved for future out of band image values. */
unsigned long ulReserved[ 6 ];

} LadybugImageInfo;


The function I need to call follows:

/**
* Parse the 6 images in a LADYBUG_VIDEOMODE_LADYBUG LadybugImage into
* 6 BGRU32 buffers. Color processing will use the current color
processing
* method.
*
* @param context The LadybugContext to access.
* @param arpszDestBuffers An array of pointers to destination
buffers that
* will hold the processed images.
* @retval pImageInfo Image information extracted from the
retrieved
* image. Use NULL to disable.
*
* @return A LadybugError indicating the success of the function.
*
* @note BGRU32 images will only be produced for the camera units that
* are included in the processing camera bit mask
* (see ladybugSetProcessingCameras()).
* @note The alpha mask (see ladybuggeom.h) will only be written to
the
* destination buffer the first time any of the Convert
functions
* are called. If you wish to use a different destination
buffer,
* call ladybugSetAlphaMasking( true ) again.
*/
LADYBUGDLL_API LadybugError
ladybugConvertToMultipleBGRU32(
LadybugContext context,
const LadybugImage* pimage,
unsigned char* arpszDestBuffers[ LADYBUG_NUM_CAMERAS ],
LadybugImageInfo* pImageInfo );


Don't worry about LadybugContext. I am able to succesfully create and
destroy it via two other API calls. I feel pretty good about the
code you supplied for creating and copying back & forth to the
unmanaged buffer. I am thinking the way I declared the LadybugImage
struct is my problem (particularily the pData member). Any
suggestions would be greatly appreciated. Let me know if you need
more info.

Thanks again,
Don
 
Hi,

c# structs:

[StructLayout(LayoutKind.Sequential)]
public struct LadyBugTimestamp
{
uint Seconds;
uint MicroSeconds;
uint CycleSeconds;
uint CycleCount;
}

public enum LadybugVideoMode
{
LADYBUG_VIDEOMODE_LADYBUG_1024x768x6,
LADYBUG_VIDEOMODE_LADYBUG_512x384x6,
LADYBUG_VIDEOMODE_LADYBUG_256x192x6,
LADYBUG_VIDEOMODE_LADYBUG_128x96x6,
LADYBUG_NUM_VIDEOMODES,
LADYBUG_VIDEOMODE_ANY
}

[StructLayout(LayoutKind.Sequential)]
public struct LadybugImageInfo
{
public uint Fingerprint;
public uint Version;
public uint TimeSeconds;
public uint TimeMicroSeconds;
public uint SequenceId;
public uint HRate;

[MarshalAs(UnmanagedType.ByValArray,SizeConst=6)]
public uint[] GainAdjust;

public uint WhiteBalance;
public uint BayerGain;
public uint BayerMap;
public uint Brightness;
public uint Gamma;
public uint SerialNum;

[MarshalAs(UnmanagedType.ByValArray,SizeConst=6)]
public uint[] Shutter;

[MarshalAs(UnmanagedType.ByValArray,SizeConst=6)]
public uint[] Reserverd;

public LadybugImageInfo(int unused)
{ // constructor
GainAdjust = new byte[6];
Shutter = new byte[6];
Reserverd = new byte[6];
}
}

[StructLayout(StructLayoutEnum.Sequential)]
public struct LadybugImage
{
public uint Cols;
public uint Rows;
public int videoMode; // cast VideModeEnums to int or parse from int
public LadybugTimeStamp timeStamp;
public LadybugImageInfo imageInfo;
public IntPtr pData;

[MarshalAs(UnmanagedType.ByValArray,SizeConst=4)]
public uint[] Reserved;

public LadybugImage(int unused)
{ // constructor
timeStamp = new LadybugTimeStamp();
imageInfo = new LadybugImageInfo(0);
Reserverd = new byte[4];
}
}

[DllImport(...)]
public static extern int ladybugConvertToMultipleBGRU32(
LadybugContext context,
ref LadybugImage image,
IntPtr[] pDestBuffers,
ref LadybugImageInfo ImageInfo );

public void test()
{
LadybugImagInfo ImageInfo = new LadybugImageInfo(0);

LadybugImage Image = new LadybugImage(0); // (*)
// eg. GetImage ( ref Image );

// Allocate unmanaged memory for DestBuffers
IntPtr[] pDestBuffers = new IntPtr[6];
for (int i=0; i<6; ++i)
pDestBuffers = Marshal.AllocHGlobal(rows*cols*4);

// Call unmanaged function
ladybugConvertToMultipleBGRU32( context, ref Image, pDestBuffers, ref
ImageInfo );

// Create managed buffer, copy pDestBuffers into it and cleanup
unmanaged buffer
byte [][] DestBuffers = new byte[6][];
for (int i=0; i<6; ++i)
{
DestBuffers = new byte[rows*cols*4];
Marshal.Copy( pDestBuffers, DestBuffers, 0, rows*cols*4 );
Marshal.HFreeGlobal( pDestBuffers );
}

// If you allocated memory for Image.pImage then cleanup
Marshal.HFreeGlobal( Image.pImage );

// result is in DestBuffers
}

You may want to rearrange it.

(*)
It's still not clear where the data comes from that you want in Image.pData
:

1. If you have a unmanaged function (eg. void GetImage (LadybugImage*
image) ) which fills an Image structure then there are 2 options:
a. the function allocates memory for pData, then you don't have to
allocate memory yourself and the dll should export a function that frees the
data it allocated (eg. FreeImage(...) )
b. the caller must allocate memory for pData before calling GetImage,
then you need to allocate memory yourself:
Image.pData = Marshal.AllocHGlobal( Image.Rows * Image.Cols * 6 );

2. If the imagedata comes from a managed type, you'll need to allocate
memory and use Marshal.Copy or Marshal.WriteByte to marshal the data.

If you don't want to use parameter ImageInfo* then you can declare it as an
IntPtr and pass IntPtr.Zero.

HTH,
Greetings
 
Hi again

It turns out the creators of the Ladybug API have built in a versioning scheme using macros in the C++ environment. They apparently did not build the API to be used from outside of C++. The method names are transformed during the compile process. I was calling a method called ladybugConvertToMultipleBGRU32, based on the documentation and what I found in the .h file. However, in the compiled dll that I am using, the method I want is ladybugConvertToMultipleBGRU32_1_1_1_1_1_1_2_2. Their versioning scheme accounts for changes to structs that are passed as parameters. Somehow (I don't fully understand it), when I made my call to the ladybugConvertToMultipleBGRU32 method, the API was able to tell that I was trying to call the original version of the method and that there is a newer version that I should be using instead. This resulted in a message box (probably a Win API call, since I didn't have any messagebox code) telling me that I am trying to use an old version and should recompile with a newer version

I just wanted to thank you for all of your help. The advice you gave for parameter #3 (IntPtr[] arpszDestBuffers): type to use; and allocating/copying between managed/unmanaged helped alot

Thanks again
Don
 
Back
Top