Liz,
I think it is quickly getting out of my/our league. The SAFEARRAY isn't
native to .NET C#, and this is what someone had to do to get it to work in
C# (sorry for the long post, but thought someone would find it useful).
Seems like a lot of effort, so I'll start another post on other options.
Thanks,
Jim Moseley
------------------
from http://social.msdn.microsoft.com/Forums/en-US/clr/thread/6641abfc-3a9c-4976-a523-43890b2b79a2/
Last post by JLetz:
I can post excerpts from the code that I used but it is pretty specific to
my application. I am trying to write a Visual Studio 2005 C# program that
interfaces with a Visual Studio 6.0 C library (.dll). In particular, I want
to call a function that has the following signature:
short WINAPI DoInsert(
HANDLEDATA *HandleRec,
SAFEARRAY **psa,
char *err_msg,
unsigned short *relrecnum,
long custom_options)
This is the only use I have for a SAFEARRAY type. The SAFEARRY is a single
dimension array of structures. The lower bound of the single dimension is
always 0. The upper bound of the single dimension varies for each call.
Each element of the array represents a variable length record. However,
the record is represented by a fixed length structure that contains a pointer
to a variable length string and some other fixed length members.
I am using Platform Invoke to call the library function. Following is the
definition of the SAFEARRAY header structure and the declaration of the function
I want to call:
[StructLayout(LayoutKind.Sequential)]
struct SafeArray
{
public ushort dimensions; // Count of dimensions in the SAFEARRAY
public ushort features; // Flags to describe SAFEARRAY usage
public uint elementSize; // Size of an array element
public uint locks; // Number of times locked without unlocking
public IntPtr dataPtr; // Pointer to the array data
public uint elementCount; // Element count for first (only) dimension
public int lowerBound; // Lower bound for first (only) dimension
}
[DllImport("WPApply.dll". EntryPoint="DoOnsert")]
static extern short DoInsert(
ref HandleData databaseHandles,
ref IntPtr safeArrayPtr,
StringBuilder message;
ref short relativeRecordNumber,
int customOptionBits)
At the time of the call to DoInsert, the COM Task memory contains the following
memory allocations:
1. A block containing the SAFEARRAY header, which is described by the
SafeArray structure.
2. A block containing an array of fixed length structures, which represent
the array elements.
3. A separate block of memory for each variable length string that is
pointed to from one of the fixed length structures.
Since the first block never changes in size, I allocate once at the beginning
of the program and it remains allocated until the end of the program. It
is allocated by a statement similar to the following:
IntPtr safeArrayPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(SafeArray)));
The size of the second block is dependent on the number of elements in the
array. This block is originally allocated based on the number of elements
in the array for the first call to DoInsert. The block remains allocated
after the call to DoInsert. For subsequent calls, if an array with a greater
number of elements is required, the block is deallocated and then reallocated
to the newer, larger size; otherwise, the block is just reused. This logic
is contained in a separate function as follows:
public void AllocateRecordBuffer(
int recordCount)
{
// Check if required number of records exceeds current capacity of data
buffer
if (recordCount > recordBufferCount)
{
// Current record data buffer capacity exceeded -- free existing
data buffer
Marshal.FreeCoTaskMem(recordBufferPtr);
// Allocate new record data buffer with new capacity
int recordSize =
WpInfoxData.FIXED_LENGTH_DATA_LENGTH * Marshal.SizeOf(typeof(byte))
+
Marshal.SizeOf(typeof(IntPtr));
recordBufferPtr = Marshal.AllocCoTaskMem(recordCount * recordSize);
// Indicate current maximum number of records that can fit in data
buffer
recordBufferCount = recordCount;
}
}
In this function, recordCount indicated the required capacity of this block
measured in records. The recordBufferCount value indicates the specified
current capacity of the memeory block also specified in records. This value
is initialized to 0 before the first call to this function. The class that
holds the record data is called WpInfoxData and the member called FIXED_LENGTH_DATA_LENGTH
is the length of the fixed data not including the pointer to th variable
length.
Now it is time to call DoInsert. The array records is an array of WpInfoxData
elements. The array is passed as a SAFEARRAY to DoInsert as follows:
// Ensure record buffer is large enough
AllocateRecordBuffer(records.Count);
// Build SAFEARRAY header for array of records
SafeArray safeArray;
safeArray.dimensions = 1;
safeArray.features = 0;
safeArray.elementSize =
(uint)((WpInfoxData.FIXED_LENGTH_DATA_LENGTH * Marshal.SizeOf(typeof(byte)))
+
Marshal.SizeOf(typeof(IntPtr)));
safeArray.locks = 0;
safeArray.dataPtr = recordBufferPtr;
safeArray.elementCount = (uint)records.Count;
safeArray.lowerBound = 0;
// Build SAFEARRAY array data
int offset = 0;
for (int i = 0; i < records.Count; i += 1)
{
// Add record to array buffer
offset = records.AddToRecordBuffer(recordBufferPtr, offset);
}
// Copy SAFEARRAY header for use by DoInsert
Marshal.StructureToPtr(safeArray, safeArrayPtr, false);
The AddToRecordBuffer method is a very repetive function that packs a large
number of data elements into the record buffer. The following provides a
sample of how a few such elements are packed into the record buffer.
public int AddToRecordBuffer(
IntPtr bufferPtr,
int offset)
{
// Check if a variable length data buffer already exists
if (varDataPtr != IntPtr.Zero)
{
// Variable length data buffer already exists -- free the buffer
Marshal.FreeCoTaskMem(varDataPtr);
}
// Allocate variable length data buffer and copy variable length byte
array into it
varDataPtr = Marshal.AllocCoTaskMem(bytes.Length * Marshal.SizeOf(typeof(byte)));
Marshal.Copy(bytes, 0, varDataPtr, bytes.Length);
// Store record number in data buffer and update offset
Marshal.WriteInt32(bufferPtr, offset, recordNumber);
offset += Marshal.SizeOf(typeof(int));
// Store relative record number in data buffer and update offset
Marshal.WriteInt16(bufferPtr, offset, relativeRecordNumber);
offset += Marshal.SizeOf(typeof(short));
// Store transaction type in data buffer and update offset
Marshal.WriteByte(bufferPtr, offset, transactionType);
offset += Marshal.SizeOf(typeof(byte));
// Store state array in data buffer and update offset
for (int i = 0; i < state.Length; i += 1)
{
// Store byte of state array in data buffer and update offset
Marshal.WriteByte(bufferPtr, offset, state);
offset += Marshal.SizeOf(typeof(byte));
}
// Store filler byte in data buffer and update offset
Marshal.WriteByte(bufferPtr, offset, 0);
offset += Marshal.SizeOf(typeof(byte));
// Store flags in data buffer and update offset
uint temp = flags;
for (int i = 0; i < sizeof(uint); i += 1)
{
// Store byte of flags in data buffer and update offset
Marshal.WriteByte(bufferPtr, offset, (byte)(temp & 0xff));
offset += Marshal.SizeOf(typeof(byte));
// Shift next byte into place
temp >>= 8;
}
// Store variable length data length in data buffer and update offset
Marshal.WriteInt32(bufferPtr, offset, bytes.Length);
offset += Marshal.SizeOf(typeof(int));
// Store variable length data buffer pointer in data buffer and update
offset
Marshal.WriteIntPtr(bufferPtr, offset, varDataPtr);
offset += Marshal.SizeOf(typeof(IntPtr));
// Return updated offset
return offset;
}
The call to DoInsert is made as follows:
short status = DoInsert(ref lsdbHandles2, ref safeArrayPtr, sbMessage,
ref relativeRecordNumber, customOptionBits);
Following the call, the memory for the SAFEARRAY header and the array buffer
remains but the memory for each variable length data buffer is freed as follows:
// Free memory allocated for each record
foreach (WpInfoxData record in records)
{
// Free memory allocated for variable length data for record
record.FreeVarDataMemory();
}
The FreeVarDataMemory function is as follows:
public void FreeVarDataMemory()
{
// Check if variable length data buffer is allocated
if (varDataPtr != IntPtr.Zero)
{
// Variable length data buffer exists -- free the buffer
Marshal.FreeCoTaskMem(varDataPtr);
varDataPtr = IntPtr.Zero;
}
}
I realize this is sketchy and fairly explicit to my application, but I hope
it helps.
----------------------