📜 ⬆️ ⬇️

.NET and working with unmanaged code. Part 2

.NET and working with unmanaged code. Part 2

Those who have not read the first part - read

After completing the first part of the work, creating structures, the task was complicated for me. The library with which I needed to work - allocated an array, the size of which was not known until the launch. He was dynamic, i.e. the more data, the more array. Here the task became more interesting, because the old way, when it was enough to use a structure in which this size was specified, was no longer appropriate.

Then I began to study marshaling further and found some more methods in the Marshal class that helped me solve the problem.
')
And so, the task itself:
- the input library takes a pointer to an array of pointers (void *) o_O
- the contents of the array - this pointers to other arrays, for example, to an array of characters, where the file address is specified, pointers to int arrays, which contain some data

Because I would often have to call some code, I created a special static class UnMemory, its code is below

/// <summary>
///
/// </summary>
public static class UnMemory
{
/// <summary>
///
/// </summary>
private static Queue< IntPtr > queue = new Queue< IntPtr >();

public static void Enqueue( IntPtr ptr)
{
queue.Enqueue(ptr);
}

private static void FreeIntPtr( IntPtr ptr)
{
if (ptr != IntPtr .Zero)
Marshal.FreeCoTaskMem(ptr);
}

/// <summary>
///
/// </summary>
public static void FreeMemory()
{
while (queue.Count > 0)
{
IntPtr temp = queue.Dequeue();
// ,
Marshal.FreeCoTaskMem(temp);
}
}
}


* This source code was highlighted with Source Code Highlighter .


In this class, I defined a queue in which I would add all the pointers to the generated unmanaged memory, so that at the end I would call its static FreeMomory () method to completely clear the allocated memory. To add a pointer to the queue, you must call UnMemory.Enqueue (ptr);
It seemed to me more convenient than to refer to the signs to clear each of them. After all, you can skip something and get a memory leak.

Also, I needed another class, which I call UnMemory, which will allocate space in the unmanaged memory and fill it with data, and of course read from it.
Its code is below
/// <summary>
///
/// </summary>
/// <typeparam name="T"> </typeparam>
public static class UnMemory<T>
where T : struct
{

/// <summary>
///
/// </summary>
/// <param name="memory_object"> </param>
/// <param name="ptr"></param>
/// <typeparam name="T"> </typeparam>
public static void SaveInMem(T memory_object, ref IntPtr ptr)
{
if ( default (T).Equals(memory_object))
{
//
ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf( typeof (T)));
UnMemory.Enqueue(ptr);
return ;
}

if (ptr == IntPtr .Zero)
{
//
ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf( typeof (T)));

//
Marshal.StructureToPtr(memory_object, ptr, false );
}
else
{
//
Marshal.StructureToPtr(memory_object, ptr, true );
}

UnMemory.Enqueue(ptr);
}

/// <typeparam name="T">IntPtr, int, float</typeparam>
/// <exception cref="System.ArgumentException"> #1 IntPtr, int, float</exception>
public static void SaveInMem2(T[] managedArray, ref IntPtr pnt)
{
Debug.Assert(managedArray != null , " Null" );
Debug.Assert(managedArray.Length != 0, " 0" );

if (pnt == IntPtr .Zero)
{
// . = *
//int size = Marshal.SizeOf(typeof(T)) * managedArray.Length;
int size = Marshal.SizeOf(managedArray[0]) * managedArray.Length;
pnt = Marshal.AllocCoTaskMem(size);
}

// , Marshal.Copy
if ( typeof (T) == typeof ( int ))
{
int [] i = managedArray as int [];
Marshal.Copy(i, 0, pnt, i.Length);
}
else if ( typeof (T) == typeof ( byte ))
{
byte [] b = managedArray as byte [];
Marshal.Copy(b, 0, pnt, b.Length);
}
else if ( typeof (T) == typeof ( float ))
{
float [] f = managedArray as float [];
Marshal.Copy(f, 0, pnt, f.Length);
}
else if ( typeof (T) == typeof ( char ))
{
//
byte [] b = Encoding .Default.GetBytes(managedArray as char []);
Marshal.Copy(b, 0, pnt, b.Length);
}
else if ( typeof (T) == typeof ( IntPtr ))
{
IntPtr [] p = managedArray as IntPtr [];
Marshal.Copy(p, 0, pnt, p.Length);
}
else
throw new ArgumentException( " #1 IntPtr, int, float char" );

// ,
UnMemory.Enqueue(pnt);
}

/// <summary>
///
/// </summary>
/// <param name="ptr"></param>
/// <param name="type"> </param>
/// <returns> </returns>
public static T ReadInMem( IntPtr ptr)
{
return (T)Marshal.PtrToStructure(ptr, typeof (T));
}

public static T[] ReadInMem2( IntPtr ptr, int size)
{
if ( typeof (T) == typeof ( int ))
{
int [] memInt = new int [size];
Marshal.Copy(ptr, memInt, 0, size);
return memInt as T[];
}
else if ( typeof (T) == typeof ( byte ))
{
byte [] memByte = new byte [size];
Marshal.Copy(ptr, memByte, 0, size);
return memByte as T[];
}
else if ( typeof (T) == typeof ( float ))
{
float [] memFloat = new float [size];
Marshal.Copy(ptr, memFloat, 0, size);
return memFloat as T[];
}
else if ( typeof (T) == typeof ( IntPtr ))
{
IntPtr [] memIntPtr = new IntPtr [size];
Marshal.Copy(ptr, memIntPtr, 0, size);
return memIntPtr as T[];
}
else
throw new ArgumentException( " #1 int, float char" );
}

/// <summary>
///
/// </summary>
public static class UnArray
{
/// <summary>
///
/// </summary>
/// <typeparam name="T"> </typeparam>
/// <param name="array"> </param>
/// <returns> </returns>
public static T[,] Rank1_Rank2(T[] array, int x, int y)
{
T[,] res = new T[x, y];
int size = Buffer.ByteLength(array);
Buffer.BlockCopy(array, 0, res, 0, size);
return res;
}

/// <summary>
///
/// </summary>
/// <typeparam name="T"> </typeparam>
/// <param name="array"> </param>
/// <returns> </returns>
public static T[] ToRank1(T[,] array, int x, int y)
{
T[] res = new T[x * y];
int size = Buffer.ByteLength(array);
Buffer.BlockCopy(array, 0, res, 0, size);
return res;
}

/// <summary>
///
/// </summary>
/// <typeparam name="T"> </typeparam>
/// <param name="array"> </param>
/// <returns> </returns>
public static T[, ,] Rank1_Rank3(T[] array, int x, int y, int z)
{
T[, ,] res = new T[x, y, z];
int size = Buffer.ByteLength(array);
Buffer.BlockCopy(array, 0, res, 0, size);
return res;
}

/// <summary>
///
/// </summary>
/// <typeparam name="T"> </typeparam>
/// <param name="array"> </param>
/// <returns> </returns>
public static T[] ToRank1(T[, ,] array, int x, int y, int z)
{
T[] res = new T[x * y * z];
int size = Buffer.ByteLength(array);
Buffer.BlockCopy(array, 0, res, 0, size);
return res;
}

/// <summary>
///
/// </summary>
/// <typeparam name="T"> </typeparam>
/// <param name="array"> </param>
/// <returns> </returns>
public static T[, , ,] Rank1_Rank4(T[] array, int x, int y, int z, int w)
{
T[, , ,] res = new T[x, y, z, w];
int size = Buffer.ByteLength(array);
Buffer.BlockCopy(array, 0, res, 0, size);
return res;
}

/// <summary>
///
/// </summary>
/// <typeparam name="T"> </typeparam>
/// <param name="array"> </param>
/// <returns> </returns>
public static T[] ToRank1(T[, , ,] array, int x, int y, int z, int w)
{
T[] res = new T[x * y * z * w];
int size = Buffer.ByteLength(array);
Buffer.BlockCopy(array, 0, res, 0, size);
return res;
}
}
}


* This source code was highlighted with Source Code Highlighter .


This class is generalized, which made it possible not to create a set of methods for each type separately. The meaning of this class is that it converts arrays from managed memory to unmanaged and vice versa. The SaveInMem method saves the structure we pass to it, the SaveInMem2 method saves an array, such as intPtr, Int, float ... The limitation is represented by the Marshal.Copy method itself, which implements copying separately for int, separately for byte and some others. For each type, the poet needed to make his own call, such as if (typeof (T) == typeof (int))

The ReadInMem and ReadInMem2 methods for reading structures and arrays, similar to SaveMem and SaveMem2.

As I said in the first part of the article, I had to work with multidimensional arrays, but the marshaling does not allow this, it can read and write only one-dimensional arrays. Therefore, I create a subclass of UnArray, which is also generalized, has several methods that create arrays from one-dimensional to two-dimensional, three-dimensional, four-dimensional and vice versa. True, I needed all these methods more in the first part, in the second, when working with arrays, they were hardly useful, although several methods could be added, then they would be useful.

And so, now go directly to the challenge itself.
From C, the call looks like this:
extern "C" int __import TkzIvc( void *mpGS[]);

* This source code was highlighted with Source Code Highlighter .


For C #, this call will look like this:
[DllImport( @"DllTkzIvc.dll" )]
private static extern int _TkzIvc([In] IntPtr mpGS);


* This source code was highlighted with Source Code Highlighter .


It remains the most simple. Make a wrapper for use

[StructLayout(LayoutKind.Sequential)]
public class mpSh_Struct : IDisposable
{
private IntPtr [] mpSh = new IntPtr [5];
private int size_vetv; //

/// <summary>
///
/// </summary>
/// <value> 255 </value>
/// <exception cref="System.ArgumentOutOfRangeException">, , 255 </exception>
public string PathSh
{
get
{
return Marshal.PtrToStringAnsi( this .mpSh[0]);
}
set
{
if ( String .IsNullOrEmpty( value ) || value .Length > 255)
throw new ArgumentOutOfRangeException( " 255 " );
this .mpSh[0] = Marshal.StringToHGlobalAnsi( value );
}
}

///<summary>
/// int[]
///</summary>
public int [] TypeV
{
get { return UnMemory< int >.ReadInMem2( this .mpSh[1], this .size_vetv); }
set { UnMemory< int >.SaveInMem2( value , ref this .mpSh[1]); }
}

/// <summary>
/// char u1[][6]
/// </summary>
/// <value> 5 </value>
public string [] u1
{
get
{
//
byte [] mem = UnMemory< byte >.ReadInMem2( this .mpSh[2], this .size_vetv * 6);
// , string[]
int length = this .size_vetv;
//
string [] res = new string [length];
for ( int i = 0; i < length; i++)
{
// , String
res[i] = Encoding .Default.GetString(mem, i * 6, 6).TrimEnd( '\0' );
}
return res;
}
set
{
// char[]
char [] res = new char [ value .Length * 6];
for ( int i = 0; i < value .Length; i++)
{
if ( value [i] != null )
value [i].CopyTo(0, res, i * 6, value [i].Length);
}
//
UnMemory< char >.SaveInMem2(res, ref this .mpSh[2]);
}
}

/// <summary>
/// float[]
/// </summary>
public float [] EK1B1
{
get { return UnMemory< float >.ReadInMem2( this .mpSh[4], this .size_vetv); }
set { UnMemory< float >.SaveInMem2( value , ref this .mpSh[4]); }
}

public int [] ParamSh
{
get { return UnMemory< int >.ReadInMem2( this .mpSh[3], 6); }
set
{
this .size_vetv = value [0]; // (ParamSh[0])
UnMemory< int >.SaveInMem2( value , ref this .mpSh[3]);
}
}

/// <summary>
///
/// </summary>
public bool Read( out string errorText)
{
try
{
IntPtr t = new IntPtr ();
UnMemory< IntPtr >.SaveInMem2( this .mpSh, ref t);

_TkzIvc(t);

mpSh = UnMemory< IntPtr >.ReadInMem2(t, this .mpSh.Length);

int [] paramSh = this .ParamSh; //
this .size_vetv = paramSh[0]; //
errorText = String .Empty;
return true ;
}
catch (DllNotFoundException)
{
errorText = " . " ;
return false ;
}
catch (Exception exp)
{
errorText = exp.Message;
return false ;
}
}

public void Dispose()
{
//
UnMemory.FreeMemory();

// ,
for ( int i = 1; i < 5; i++)
{
IntPtr ptr = mpSh[i];
//
UnMemory.FreeIntPtr(ptr);
}
}
}

* This source code was highlighted with Source Code Highlighter .


NOTE that for plain text strings, I used Marshal.StringToHGlobalAnsi (text). Because This is a special Marshal class method for reading and writing a plain text string that ends with a '\ 0' character.

An array of pointers is stored in mpSh, and for all fields, get and set methods are implemented that write and read unmanaged memory almost transparently from the class itself. The Read method transfers an array of pointers to unmanaged memory in order to later read the data from it (when the library is called, this data is filled in). After calling Read, we can read the variable we need by simply referring to the desired field.

However, there is one restriction ... it is better to get a link to the array (int [] paramSh = this.ParamSh), and only then work with it, rather than constantly referring to the field (this.size_vetv = this.ParamSh [0]). Because when accessing, the data will be re-read from unmanaged memory. Despite the fact that it goes fast, if there are many calls, it can take a long time.

PS: this article has not been reviewed by CustomMarshaling. Its essence is that you can fully customize how a certain structure should be maintained, with its help you can support different versions of your libraries. For example, save DateTime as a String in a certain format, etc. Such cases are less common, so a separate article can be highlighted for this.

Unfortunately I can not attach the source to the article, because This will not approve the manual :) But I tried to bring the maximum code so that without source codes it was clear how to work with the marshaling. If you find something incomprehensible, please write and I will answer your questions.

Source: https://habr.com/ru/post/85096/


All Articles