Laden...

Marshaler für UTF-8 Strings

Erstellt von der_alex1980 vor 13 Jahren Letzter Beitrag vor 13 Jahren 3.308 Views
D
der_alex1980 Themenstarter:in
4 Beiträge seit 2010
vor 13 Jahren
Marshaler für UTF-8 Strings

Hallo,

ich arbeite gerade an einem Projekt, bei dem ich per Interop auf eine unmanaged C++ Bibliothek zugreife, welche Unicode strings verwendet. Das ganze soll nicht nur auf Windows, sondern auch auf Linux mit Mono laufen. Allerdings verhalten sich die Frameworks leicht unterschiedlich, wenn Unicode strings an unmanaged Funktionen übergeben werden. .NET übergibt strings im UTF-16, Mono hingegen im UTF-8 Format. Um kompatibel zu sein, habe ich beschlossen alle Strings grundsätzlich als UTF-8 zu übergeben und dazu einen Marshaler geschrieben. Der Marshaler wird einfach mit dem [MarshalAs] Attribut an die Deklaration der extern Funktion angehängt. Die Konvertierung der Strings übernimmt dann die Laufzeitumgebung automatisch beim Aufruf der Funkton.

Der Marshaler kann im Prinzip in allen Projekten verwendet werden, bei denen der unmanaged Code Strings im UTF-8 Format erwartet.

Getestet hab ich den Code bisher nur unter Linux mit Mono 2.6. Er sollte allerdings auch auf Windows funktionieren, hoff ich jedenfalls.

Hier der Code:



using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Wrapper
{
	/// <summary>
	/// Konvertiert einen .net String in eine nullterminierte UTF-8 Bytesequenz und umgekehrt.
	/// </summary> 
	public class Utf8Marshaler : ICustomMarshaler
	{
		private static object thisLock = new object();
		private static Utf8Marshaler marshaler;
		
		public void CleanUpManagedData(object ManagedObj) {}
		
		public void CleanUpNativeData(IntPtr pNativeData)
		{
			Marshal.FreeHGlobal(pNativeData);
		}
		
		// Wird nicht benötigt
		public int GetNativeDataSize()
		{
			return -1;
		}
		
		/// <summary>
		/// Konvertiert einen .net String in ein nullterminiertes UTF-8 Bytearray, kopiert dieses auf 
		/// den unmanaged Heap und gibt einen Zeiger auf den Speicherbereich zurück 	 
		/// </summary>
		public IntPtr MarshalManagedToNative(object ManagedObj)
		{
			if ( ManagedObj == null )
				return IntPtr.Zero;			
			if ( ! (ManagedObj is string) )
				throw new MarshalDirectiveException("This Marshaler only works on strings");
	
			string myString = (string) ManagedObj;
			
			// Aus Laufzeitgründen wird die Größe des Puffers mit GetMaxByteCount berechnet. Dadurch 
			// kann allerdings kurzzeitig mehr Speicher belegt werden als tatsächlich benötigt wird
			Encoding encoder = new UTF8Encoding();
			int nativeLength = encoder.GetMaxByteCount(myString.Length) + 1;
			byte[] buffer = new byte[nativeLength];
			nativeLength = encoder.GetBytes(myString, 0, myString.Length, buffer, 0) + 1;
		
			IntPtr ptrHeap = Marshal.AllocHGlobal(nativeLength);
			Marshal.Copy(buffer, 0, ptrHeap, nativeLength);
						
			return ptrHeap;
		}
		
		/// <summary>
		/// Konvertiert eine unmanaged UTF-8 Bytesequenz in einen string und gibt diesen zurück.
		/// </summary>
		public object MarshalNativeToManaged(IntPtr pNativeData)
		{
			if ( pNativeData == IntPtr.Zero ) 
				return null;

			Encoding encoder = new UTF8Encoding();
			string result;
			unsafe 
			{
				int length = 0;
				sbyte* ptrNativeBytes = (sbyte*) pNativeData;
				for ( sbyte* ptrByte = ptrNativeBytes; *ptrByte != 0; ptrByte++ ) 
					length++;
					
				result = new string(ptrNativeBytes, 0, length, encoder);
			} 	
			
			return result;
		}
		
		/// <summary>
		/// Initialisiert den Marshaler. Diese Methode wird von der Laufzeitumgebung benötigt. 
		/// </summary>
		public static ICustomMarshaler GetInstance(string cookie)
		{
			if ( marshaler == null )
			{
				lock ( thisLock )
				{
					if ( marshaler == null )
						marshaler = new Utf8Marshaler();
				}
			}
			
			return marshaler;
		}
	}
}

Anwendungsbeispiel:

Ein möglicher Anwendungsfall könnte folgendermaßen aussehen. Hier wird einer Wrapperfunktion ein char Array übergeben, von dem erwartet wird, dass es in UTF-8 vorliegt.


C++ Code

class MyClass
{
   ...
   public:
      void SetText(QString s);
}


// C Wrapper für MyClass.SetText. Wird von der .NET Anwendung aufgerufen
extern "C" void MyClass_setText(char* s, MyClass* instance)
{
    instance->setText(QString::fromUtf8(s));
}  

Zur Deklaration der Funktion in C# wird das MarshalAs Attribut verwendet, welches vor dem string Parameter platziert wird.


[DllImport("mylib"]
static extern void MyClass_setText(
                [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(Wraper.Utf8Marshaler))]
                string s,
                IntPtr instance);