C# FMOD Playing stream in realtime - c#

i am trying to play a stream in real time ( I keep appedning data to it as it comes in from a nexternal source) but no matter what FMOD doesn't want to carry on playing after the first chunk that got loaded, it seems as it is copying the memory stream/decoding it before playing, then as it is playing it doesn't use my stream anymore.
I am using the following to play my stream:
var exinfo = new FMOD.CREATESOUNDEXINFO();
exinfo.cbsize = Marshal.SizeOf(exinfo);
exinfo.length = (uint)_buffer.Length;
_result = System.createStream(_buffer, MODE.CREATESTREAM | MODE.OPENMEMORY_POINT , ref exinfo, ref _sound);
FMODErrorCheck(_result);
_result = System.playSound(FMOD.CHANNELINDEX.FREE, _sound, false, ref _channel);
FMODErrorCheck(_result);
But no matter what, it only plays the amount of data that is in the stream at the point of calling playSound.
Can anyone know how to modify the buffer in real time? After the stream has started playing...?

I would recommend you check out the "usercreatedsound" example that ships with FMOD, it should do what you require.
The basic idea is you define the properties of the sound you wish to play in the CreateSoundExInfo structure and provide it with callbacks which you can use to load / stream data from wherever you like.
Function pointer:
private FMOD.SOUND_PCMREADCALLBACK pcmreadcallback = new FMOD.SOUND_PCMREADCALLBACK(PCMREADCALLBACK);
Callback used to populate the FMOD sound:
private static FMOD.RESULT PCMREADCALLBACK(IntPtr soundraw, IntPtr data, uint datalen)
{
unsafe
{
short *stereo16bitbuffer = (short *)data.ToPointer();
// Populate the 'stereo16bitbuffer' with sound data
}
return FMOD_OK;
}
Code to create the sound that will use the callback:
// ...Usual FMOD initialization code here...
FMOD.CREATESOUNDEXINFO exinfo = new FMOD.CREATESOUNDEXINFO();
// You define your required frequency and channels
exinfo.cbsize = Marshal.SizeOf(exinfo);
exinfo.length = frequency * channels * 2 * 5; // *2 for sizeof(short) *5 for 5 seconds
exinfo.numchannels = (int)channels;
exinfo.defaultfrequency = (int)frequency;
exinfo.format = FMOD.SOUND_FORMAT.PCM16;
exinfo.pcmreadcallback = pcmreadcallback;
result = system.createStream((string)null, (FMOD.MODE.DEFAULT | FMOD.MODE.OPENUSER | FMOD.MODE.LOOP_NORMAL), ref exinfo, ref sound);
That should be sufficient to get you going, hope this helps.

If you wish to stream raw data, not PCM data you could achieve this by overriding the FMOD file system. There are two ways to achieve this, the first is by setting the file callbacks in the CreateSoundExInfo structure if this is for one specific file. The second is you can set the file system globally for all FMOD file operations (incase you want to do this with multiple files).
I will explain the latter, it would be trivial to switch to the former though. Refer to the "filecallbacks" FMOD example for a complete example.
Function pointers:
private FMOD.FILE_OPENCALLBACK myopen = new FMOD.FILE_OPENCALLBACK(OPENCALLBACK);
private FMOD.FILE_CLOSECALLBACK myclose = new FMOD.FILE_CLOSECALLBACK(CLOSECALLBACK);
private FMOD.FILE_READCALLBACK myread = new FMOD.FILE_READCALLBACK(READCALLBACK);
private FMOD.FILE_SEEKCALLBACK myseek = new FMOD.FILE_SEEKCALLBACK(SEEKCALLBACK);
Callbacks:
private static FMOD.RESULT OPENCALLBACK([MarshalAs(UnmanagedType.LPWStr)]string name, int unicode, ref uint filesize, ref IntPtr handle, ref IntPtr userdata)
{
// You can ID the file from the name, then do any loading required here
return FMOD.RESULT.OK;
}
private static FMOD.RESULT CLOSECALLBACK(IntPtr handle, IntPtr userdata)
{
// Do any closing required here
return FMOD.RESULT.OK;
}
private static FMOD.RESULT READCALLBACK(IntPtr handle, IntPtr buffer, uint sizebytes, ref uint bytesread, IntPtr userdata)
{
byte[] readbuffer = new byte[sizebytes];
// Populate readbuffer here with raw data
Marshal.Copy(readbuffer, 0, buffer, (int)sizebytes);
return FMOD.RESULT.OK;
}
private static FMOD.RESULT SEEKCALLBACK(IntPtr handle, int pos, IntPtr userdata)
{
// Seek your stream to desired position
return FMOD.RESULT.OK;
}
Implementation:
// Usual init code here...
result = system.setFileSystem(myopen, myclose, myread, myseek, 2048);
ERRCHECK(result);
// Usual create sound code here...

Related

How to copy float* to IntPtr?

Short description of my task:
As parameter of my function I got some buffer (IntPtr).
I need to extract some information from this buffer and copy information to audioFrame.AudioBuffer buffer (IntPtr).
Problem:
Needed information placed in channelData[c] (float*), I need to copy this information to destStart (IntPtr).
Code:
private void SomeFunc(IntPtr buffer)
{
...
AudioFrame audioFrame; // audioFrame.AudioBuffer is IntPtr
...
unsafe
{
float** channelData = (float**)buffer.ToPointer();
for (int c = 0; c < 2; c++)
{
IntPtr destStart = new IntPtr(audioFrame.AudioBuffer.ToInt64() + (c * audioFrame.ChannelStride));
Marshal.Copy(channelData[c], 0, destStart, audioFrame.NumSamples); ///< problem in this line, channelData[c] is float*
}
}
...
}
Edit
Little more context: I got this buffer from CEF (https://github.com/cefsharp/CefSharp). In fact this function work as callback. When I got new audio data I need to send this data throught NDI (https://www.ndi.tv/)
AudioFrame is wrapper over the NDI structure
public struct audio_frame_v2_t
{
// The sample-rate of this buffer
public int sample_rate;
// The number of audio channels
public int no_channels;
// The number of audio samples per channel
public int no_samples;
// The timecode of this frame in 100ns intervals
public Int64 timecode;
// The audio data
public IntPtr p_data;
// The inter channel stride of the audio channels, in bytes
public int channel_stride_in_bytes;
// Per frame metadata for this frame. This is a NULL terminated UTF8 string that should be
// in XML format. If you do not want any metadata then you may specify NULL here.
public IntPtr p_metadata;
// This is only valid when receiving a frame and is specified as a 100ns time that was the exact
// moment that the frame was submitted by the sending side and is generated by the SDK. If this
// value is NDIlib_recv_timestamp_undefined then this value is not available and is NDIlib_recv_timestamp_undefined.
public Int64 timestamp;
}
Perhaps consider spans here:
var fromSpan = new Span<float>(channelData[c], audioFrame.NumSamples);
var toSpan = new Span<float>(destStart.ToPointer(), audioFrame.NumSamples);
fromSpan.CopyTo(toSpan);
Or Buffer.MemoryCopy:
var size = sizeof(float) * audioFrame.NumSamples;
Buffer.MemoryCopy(channelData[c], destStart.ToPointer(), size, size);
(note that in both cases it would be better to include knowledge of the actual buffer sizes if you have it, to avoid buffer overflow scenarios; I've just assumed the sizes are valid, for simplicity; there's also Unsafe.CopyBlock which works a lot like Buffer.MemoryCopy)

Enumerating the shell with Next > 1

All sample codes anywhere on the net just don't question the habit of passing 1 as the first argument to an enumeration interface Next() call. Yet the documentation clearly promises that more than one items can be obtained in one call. As it can be seen in this code fragment, doing so would tremendously speed up the process of counting the files in a folder (actually, the fundamentally similar WPD interface works that way).
string FolderPath = #"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\<Phone-USB-ID>\Internal storage\Pictures\Test";
SHCreateItemFromParsingName(FolderPath, IntPtr.Zero, typeof(IShellItem).GUID, out IShellItem item);
item.BindToHandler(IntPtr.Zero, BHID_SFObject, typeof(IShellFolder).GUID, out IShellFolder folder);
folder.EnumObjects(IntPtr.Zero, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, out IEnumIDList list);
uint count = 0;
try {
do {
//var ObjectIDs = new ObjectIDLargeArray();
//var pidl = Marshal.AllocHGlobal(Marshal.SizeOf(ObjectIDs));
//Marshal.StructureToPtr(ObjectIDs, pidl, true);
//var pidl = Marshal.AllocCoTaskMem(100 * IntPtr.Size);
int hr = list.Next(100, out var pidl, out uint fetched); // <<<<<
if (hr == 0)
count += fetched;
if (fetched == 0)
break;
//Marshal.FreeHGlobal(pidl);
Marshal.FreeCoTaskMem(pidl);
}
while (true);
}
catch (Exception e) {
Console.WriteLine(e);
}
[StructLayout(LayoutKind.Sequential)]
internal class ObjectIDLargeArray {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public IntPtr[] IDs;
}
The documentation and the usage on the web is not entirely clear about whether the caller has to allocate the buffer for the IDs returned but as can be seen from the code, I tried all approaches, both a specific array and an allocated block of memory. For very small values like 2 and 3, the shell might simply return garbage in fetched. For anything larger, an access violation exception.
Just to put it into a perspective of why it's important: with a newly connected phone, no previous caching, counting the files in a folder of 500 pictures takes about 15 (!) seconds. The second time with data already cached, 3 to 4. Needless to say, even the second is unacceptable for a mere file count but 15 seconds are absolutely stupid. With the otherwise mediocre speeds of WPD but with a 100-step count, a few hundred ms at most.
Solved courtesy of the new C# 7.2 feature, in parameters:
internal interface IEnumIDList {
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
HResult Next(uint celt, in IntPtr rgelt, out uint pceltFetched);
}
That makes it play all right with both the preallocated array and the single value case. It's not really that much faster, unfortunately...

Problems with "SetupScanFileQueue" callback from SetupAPI C#

I've implemented a SetupAPI wrapper in C# that enumerates devices based on GUIDs. I have, more or less, converted the DevCon c++ example code found on MSDN to C# (Yes I very much know that this is all veyr painfully to do in .NET, but it's fun and a challenge).
I'm able to get all the appropriate information about a certain device, but a problem occurred when I reached the "SetupScanFileQueue" method.
I cant seem to be able to make the "SetupScanFileQueue" to call my callback. The method returns true, so it seems to be working.
My end goal is to get the driver files of the specific device.
Additional information:
The files appear to be added to the FileQueue correctly, I get this popup window that seems to copy the correct files.
// create a file queue so we can look at this queue later
var queueHandler = SetupOpenFileQueue();
if (queueHandler == IntPtr.Zero)
return false;
// modify flags to indicate we're providing our own queue
var deviceInstallParams = new SP_DEVINSTALL_PARAMS();
deviceInstallParams.cbSize = Marshal.SizeOf(deviceInstallParams);
if (!SetupDiGetDeviceInstallParams(handle, ref devInfo, ref deviceInstallParams))
{
error = Marshal.GetLastWin32Error();
return false;
}
// we want to add the files to the file queue, not install them!
deviceInstallParams.FileQueue = queueHandler;
deviceInstallParams.Flags |= DI_NOVCP;
if (!SetupDiGetDeviceInstallParams(handle, ref devInfo, ref deviceInstallParams))
{
error = Marshal.GetLastWin32Error();
return false;
}
// now fill queue with files that are to be installed
// this involves all class/co-installers
if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICEFILES, handle, ref devInfo))
{
error = Marshal.GetLastWin32Error();
return false;
}
// we now have a list of delete/rename/copy files
// iterate the copy queue twice - 1st time to get # of files
// 2nd time to get files
// (WinXP has API to get # of files, but we want this to work
// on Win2k too)
var scanResult = 0;
var count = 0;
var callback = new PSP_FILE_CALLBACK(PSP_FILEFOUND_CALLBACK);
var t = SetupScanFileQueue(queueHandler, SPQ_SCAN_USE_CALLBACK, IntPtr.Zero,
callback, ref count, ref scanResult);
SetupDiDestroyDriverInfoList(handle, ref devInfo, SPDIT_CLASSDRIVER);
if (queueHandler != IntPtr.Zero)
SetupCloseFileQueue(queueHandler);
The definition of my Callback is as follows:
public delegate uint PSP_FILE_CALLBACK(uint context, uint notifaction, IntPtr param1, IntPtr param2);
public static uint PSP_FILEFOUND_CALLBACK(uint context, uint notifaction, IntPtr param1, IntPtr param2)
{
//This callback is never triggered
return 0;
}
Does anyone have any suggestions to what I'm doing wrong in the "SetupScanFileQueue" function, and why the callback is never called?
Any help is very much appreciated!
Edit:
I should also have added the DllImport for the SetupScanFileQueue function:
[DllImport("setupapi.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern uint SetupScanFileQueue(IntPtr QueueHandle,
int Flags,
IntPtr Window,
PSP_FILE_CALLBACK CallbackRoutine,
int CallbackContext,
out int ScanResult
);
I've also tried it without the CallingConvention.

Getting Text from SysListView32 in 64bit

here is my code :
public static string ReadListViewItem(IntPtr lstview, int item)
{
const int dwBufferSize = 1024;
int dwProcessID;
LV_ITEM lvItem;
string retval;
bool bSuccess;
IntPtr hProcess = IntPtr.Zero;
IntPtr lpRemoteBuffer = IntPtr.Zero;
IntPtr lpLocalBuffer = IntPtr.Zero;
IntPtr threadId = IntPtr.Zero;
try
{
lvItem = new LV_ITEM();
lpLocalBuffer = Marshal.AllocHGlobal(dwBufferSize);
// Get the process id owning the window
threadId = GetWindowThreadProcessId(lstview, out dwProcessID);
if ((threadId == IntPtr.Zero) || (dwProcessID == 0))
throw new ArgumentException("hWnd");
// Open the process with all access
hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwProcessID);
if (hProcess == IntPtr.Zero)
throw new ApplicationException("Failed to access process");
// Allocate a buffer in the remote process
lpRemoteBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, dwBufferSize, MEM_COMMIT,
PAGE_READWRITE);
if (lpRemoteBuffer == IntPtr.Zero)
throw new SystemException("Failed to allocate memory in remote process");
// Fill in the LVITEM struct, this is in your own process
// Set the pszText member to somewhere in the remote buffer,
// For the example I used the address imediately following the LVITEM stuct
lvItem.mask = LVIF_TEXT;
lvItem.iItem = item;
lvItem.iSubItem = 2;
lvItem.pszText = (IntPtr)(lpRemoteBuffer.ToInt32() + Marshal.SizeOf(typeof(LV_ITEM)));
lvItem.cchTextMax = 50;
// Copy the local LVITEM to the remote buffer
bSuccess = WriteProcessMemory(hProcess, lpRemoteBuffer, ref lvItem,
Marshal.SizeOf(typeof(LV_ITEM)), IntPtr.Zero);
if (!bSuccess)
throw new SystemException("Failed to write to process memory");
// Send the message to the remote window with the address of the remote buffer
SendMessage(lstview, LVM_GETITEMText, 0, lpRemoteBuffer);
// Read the struct back from the remote process into local buffer
bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, lpLocalBuffer, dwBufferSize,IntPtr.Zero);
if (!bSuccess)
throw new SystemException("Failed to read from process memory");
// At this point the lpLocalBuffer contains the returned LV_ITEM structure
// the next line extracts the text from the buffer into a managed string
retval = Marshal.PtrToStringAnsi((IntPtr)(lpLocalBuffer +
Marshal.SizeOf(typeof(LV_ITEM))));
}
finally
{
if (lpLocalBuffer != IntPtr.Zero)
Marshal.FreeHGlobal(lpLocalBuffer);
if (lpRemoteBuffer != IntPtr.Zero)
VirtualFreeEx(hProcess, lpRemoteBuffer, 0, MEM_RELEASE);
if (hProcess != IntPtr.Zero)
CloseHandle(hProcess);
}
return retval;
}
no matter what i do retval returns empty, although lpLocalBuffer doesnt .
here is the def of ListItem :
[StructLayout(LayoutKind.Sequential)]
private struct LV_ITEM
{
public int mask;
public int iItem;
public int iSubItem;
public int state;
public int stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
internal int lParam;
internal int iIndent;
}
i tried compiling for 86x , 64bit, any cpu , nothing seems to work at all !
any idea why this might be happening ?
C# + .net4 , windows 7 64bit.
Here's a different approach to doing this - use UI Automation. It does the cross-process, cross-bitness work for you, and will work against listviews, listboxes, or pretty much any other standard Windows UI. Here's a sample app that will get the HWND from the listview under the mouse pointer, and dump the items in it. It dumps just the name of each item; with Listviews, I think you can recurse into the fields in each item if you want.
// Compile using: csc ReadListView.cs /r:UIAutomationClient.dll
using System;
using System.Windows.Automation;
using System.Runtime.InteropServices;
class ReadListView
{
public static void Main()
{
Console.WriteLine("Place pointer over listview and hit return...");
Console.ReadLine();
// Get cursor position, then the window handle at that point...
POINT pt;
GetCursorPos(out pt);
IntPtr hwnd = WindowFromPoint(pt);
// Get the AutomationElement that represents the window handle...
AutomationElement el = AutomationElement.FromHandle(hwnd);
// Walk the automation element tree using content view, so we only see
// list items, not scrollbars and headers. (Use ControlViewWalker if you
// want to traverse those also.)
TreeWalker walker = TreeWalker.ContentViewWalker;
int i = 0;
for( AutomationElement child = walker.GetFirstChild(el) ;
child != null;
child = walker.GetNextSibling(child) )
{
// Print out the type of the item and its name
Console.WriteLine("item {0} is a \"{1}\" with name \"{2}\"", i++, child.Current.LocalizedControlType, child.Current.Name);
}
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
};
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(POINT pt);
[DllImport("user32.dll")]
private static extern int GetCursorPos(out POINT pt);
}
I know this is old, but I found it while trying to solve my problem and hopefully this will help someone else.
I used the recommendation in this question, that was in C++, and slightly modified the LV_ITEM structure to make it work with 64bit in VB.NET (I haven't tested in C# but I imagine the solution is quite similar.)
Public Structure LV_ITEM64
Public mask As Integer
Public iItem As Integer
Public iSubItem As Integer
Public state As Integer
Public stateMask As Integer
Public placeholder1 As Integer
Public pszText As Integer
Public placeholder2 As Integer
Public cchTextMax As Integer
Public iImage As Integer
End Structure
Then, when declaring the instance of the structure, I used the following code to choose between 64 bit and 32 bit structures:
Dim lvi As Object
If IntPtr.Size = 4 Then
lvi = New LV_ITEM
Else
lvi = New LV_ITEM64
End If
You have clarified that you are trying to read items from a list view control in a 32 bit process into a different 64 bit process.
I have seen many questions on this topic in various forums and not one ever seemed to achieve a successful outcome.
I think your best option is to create a 32 bit executable which will be able to read out of the other program's list view.
There is at least one obstacle to overcome if your program is 32-bit and the target program is 64-bit. Or the other way around. The LVITEM declaration will be wrong, IntPtr has the wrong number of bits. Which makes Marshal.SizeOf() return the wrong value. Alignment is okay, I think, by accident. Changing the field to either int or long can fix the problem, depending on the bitness of the target program. Which you can find out by looking at the Taskmgr.exe, Processes tab. The process name is post-fixed with "*32" if it is a 32-bit process. Or simply stay out of trouble by setting your project's Target platform setting to match the target process (x86 or AnyCPU).
Debug this by using Debug + Windows + Memory + Memory1. Put "lpLocalBuffer" in the Address box and observe what you see vs what your code reads. You should definitely be able to tell from the hex view that you got the string properly. Note that if you see zeros between the string characters then the target process uses the Unicode version of the list view. Marshal.PtrToStringUnicode is then required to read it.
Sorry my response is so late but I just came across the same issue. Here is the structure I used for VB.NET which works on both 32 and 64 bit systems.
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure LV_ITEM
Public Mask As UInteger
Public Index As Integer
Public SubIndex As Integer
Public State As Integer
Public StateMask As IntPtr
Public Text As String
Public TextLength As Integer
Public ImageIndex As Integer
Public LParam As IntPtr
End Structure

converting a byte array from a native method to a managed structure

I have a c# .net 2.0 CF application that interfaces with a native DLL implementing a function like this:
struct NATIVE_METHOD_REPLY
{
int other_irrelevant_data;
int data_size;
void* data;
}
// reply_buffer will contain an array of NATIVE_METHOD_REPLY structures
// and their data.
//
// returns an error code
int Foo(NATIVE_METHOD_REPLY* reply_buffer, int reply_size);
I've implemented it in C# like this:
[StructLayout(LayoutKind.Sequential)]
internal struct NATIVE_METHOD_REPLY
{
public Int32 OtherIrrelevantData;
public Int16 DataSize;
public IntPtr DataPtr;
}
[DllImport("my_lib.dll", SetLastError = true)]
internal static extern Int32 Foo(byte[] replyBuffer, Int32 replySize);
public byte[] void Bar()
{
// data returned to the user. May be an arbitrary size.
byte[] result_buffer = new byte[256];
// data sent to Foo()
byte[] reply_buffer =
new byte[Marshal.SizeOf(typeof(NativeMethods.NATIVE_METHOD_REPLY)) +
result_buffer.Length];
NativeMethods.Foo(reply_buffer, reply_buffer.Length);
// is there a better way of doing this?
NativeMethods.NATIVE_METHOD_REPLY reply;
GCHandle pinned_reply = GCHandle.Alloc(reply_buffer,
GCHandleType.Pinned);
try
{
reply = (NativeMethods.NATIVE_METHOD_REPLY)Marshal.PtrToStructure(
pinned_reply.AddrOfPinnedObject(),
typeof(NativeMethods.NATIVE_METHOD_REPLY));
Marshal.Copy(reply.DataPtr, result_buffer, 0, reply.DataSize);
}
finally
{
pinned_reply.Free();
}
// bonus point*: is this okay to do after the Free() call?
int test = reply.OtherIrrelevantData;
return result_buffer;
}
While this works correctly, I would like to know if this is the most efficient / most correct way of implementing this function.
Is there some method converting a managed byte array to a managed structure that doesn't involve an intermediate native handle and a copy? For instance, in C++, I would just do this:
NATIVE_METHOD_REPLY* reply = reinterpret_cast< NATIVE_METHOD_REPLY* >( reply.DataPtr );
*For a bonus point, is it okay to use data in the structure after the native handle has been freed?
Thanks,
PaulH
Edit: Updated solution
[DllImport("my_lib.dll", SetLastError = true)]
internal static extern Int32 Foo(IntPtr replyBuffer, Int32 replySize);
public byte[] void Bar()
{
byte[] result_buffer = new byte[256];
int reply_buffer_len = Marshal.SizeOf(typeof(NativeMethods.NATIVE_METHOD_REPLY)) + result_buffer.Length;
IntPtr reply_buffer = Marshal.AllocCoTaskMem(reply_buffer_len);
NativeMethods.NATIVE_METHOD_REPLY reply;
try
{
NativeMethods.Foo(reply_buffer, reply_buffer_len);
reply = (NativeMethods.NATIVE_METHOD_REPLY)Marshal.PtrToStructure(
reply_buffer,
typeof(NativeMethods.NATIVE_METHOD_REPLY));
Marshal.Copy(reply.DataPtr, result_buffer, 0, reply.DataSize);
}
finally
{
Marshal.FreeCoTaskMem(reply_buffer);
}
return result_buffer;
}
The structure has a fixed size. There's no point in passing an array, just pass the structure:
[DllImport("my_lib.dll", SetLastError = true)]
internal static extern Int32 Foo(out NATIVE_METHOD_REPLY replyBuffer, Int32 replySize);
You do have a memory management problem. Who owns the pointer?
Okay, the structure is actually variable sized and the pointer points into the array. You need nother approach. Simply allocate a chunk of unmanaged memory up front instead of letting the P/Invoke marshaller copy the data into a managed array. Which is in fact a hard requirement since the garbage collector can move the array, invalidating the pointer. Call Marshal.CoTaskMemAlloc() to reserve the memory, you'll have to free it later. And change the first argument of the function to IntPtr (not out).
You'll find that marshaling the structure a lot easier too, no need to pin the memory. Don't forget to Marshal.FreeCoTaskMem() when you're done.
In C# under the full framework, you can marshal the array directly. See Default Marshaling for Arrays. I don't know what the limitations are on the Compact Framework.

Categories

Resources