How to convert c++ QString parameter to c# string

1.5k Views Asked by At

I'm trying to hook the QPainter::drawText function in another application by injecting my DLL and detour the QPainter::drawText function to my own application. I'm doing this because the other application does not expose a usable API and I want to do some basic statistical analysis on the data I'm getting.

All is working fine: I see the QPainter::drawText functions being called, but I'm unable to convert the QString parameter to anything useful. All I get is two characters when I Marshall the QString parameter as LPWStr.

I'm no C++ superhero, so I'm a bit lost. I think I'm looking at some pointer or reference because of the two characters I get for each call, but I'm not sure. After a few nights trying to make sense of it I'm close to the point of giving up.

I've demangled the QPainter::drawText function (found using Dependency Walker: ?drawText@QPainter@@QAEXABVQRect@@HABVQString@@PAV2@@Z) with https://demangler.com/ and it comes up with this function declaration:

public: void __thiscall QPainter::drawText(class QRect const &,int,class QString const &,class QRect *)

I've converted this into the following DllImport (I substituted the QRect and Qstring classes to IntPtr, because I have no idea how to convert them to C#).

    [DllImport("Qt5Gui.dll", SetLastError = true,  CharSet = CharSet.Unicode, CallingConvention = CallingConvention.ThisCall, EntryPoint = "?drawText@QPainter@@QAEXABVQRect@@HABVQString@@PAV2@@Z")]
    public static extern void QPainter_drawText(IntPtr obj, IntPtr p1, int p2, IntPtr p3, IntPtr p4);

This is what I have so far:

Detouring Qt QPainter::drawText

    LocalHook QPainter_drawTextHook;

    [DllImport("Qt5Gui.dll", SetLastError = true,  CharSet = CharSet.Unicode, CallingConvention = CallingConvention.ThisCall, EntryPoint = "?drawText@QPainter@@QAEXABVQRect@@HABVQString@@PAV2@@Z")]
    public static extern void QPainter_drawText(IntPtr obj, IntPtr p1, int p2, IntPtr p3, IntPtr p4);

    [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Unicode, SetLastError = true)]
    delegate void TQPainter_drawText(IntPtr obj, IntPtr p1, int p2, IntPtr p3, IntPtr p4);

    static void QPainter_drawText_Hooked(IntPtr obj, IntPtr p1, int p2, IntPtr p3, IntPtr p4)
    {
        var qs3 = (QString)Marshal.PtrToStructure(p3, typeof(QString));

        try
        {
            ((Main)HookRuntimeInfo.Callback).Interface.GotQPainter_drawText(qs3.ToString());
            QPainter_drawText(obj, p1, p2, p3, p4);
        }
        catch (Exception ex)
        {
            ((Main)HookRuntimeInfo.Callback).Interface.ErrorHandler(ex);
        }
    }

Create QPainter::drawText detour

            QPainter_drawTextHook = LocalHook.Create(
                                        LocalHook.GetProcAddress("Qt5Gui.dll", "?drawText@QPainter@@QAEXABVQRect@@HABVQString@@PAV2@@Z"),
                                        new TQPainter_drawText(QPainter_drawText_Hooked), 
                                        this);

            QPainter_drawTextHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });

Update 2016-1-31 Thus far I have found this (see https://github.com/mono/cxxi/blob/master/examples/qt/src/QString.cs). But now I'm getting an AccessViolationException on the Marshal.PtrToStringUni.

    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct QString
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct Data
        {
            public int @ref;
            public int alloc, size;
            public IntPtr data;
            public ushort clean;
            public ushort simpletext;
            public ushort righttoleft;
            public ushort asciiCache;
            public ushort capacity;
            public ushort reserved;
            public IntPtr array;
        }

        public Data* d;
        #endregion

        public override string ToString()
        {
            try
            {
                return Marshal.PtrToStringUni(d->array, d->alloc * 2);
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }
    }
2

There are 2 best solutions below

0
On BEST ANSWER

Thanks to the helpful article https://woboq.com/blog/qstringliteral.html, which explains the QString data structure in Qt5 I've managed to get the hook working:

    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct QString
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct QStringData
        {
            public int @ref;
            public int size;
            public uint alloc;
            public uint capacityReserved;
            public fixed byte data[128];
        }

        public QStringData* data;

        public override string ToString()
        {
            try
            {
                var bytes = new byte[data->size * 2];
                Marshal.Copy((IntPtr)data->data, bytes, 0, data->size * 2);
                return Encoding.Unicode.GetString(bytes);
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }
    }
1
On

QString is the complex type. You can try to import QString::FromUtf16 for create QString and pass IntPtr to your function

string str = "Test";
var p2 = new IntPtr(QString.Utf16(str,str.Length));

EDIT: Also, you can try https://github.com/ddobrev/QtSharp for this