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

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;
}
}
}

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

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;
}
}
}

Related

C# deezer native api: adapting to C#

I'm trying to use a C# class that wraps C++ native api into CLI C# class.
It seems that there are some problems (it really near to be working) and would like some help to find the problem.
Here is the wrapper's code
using System;
using System.Collections;
using System.Runtime.InteropServices;
// make this binding dependent on WPF, but easier to use
using System.Windows.Threading;
// http://www.codeproject.com/Articles/339290/PInvoke-pointer-safety-Replacing-IntPtr-with-unsaf
namespace Deezer
{
#region Enums
public enum CONNECT_EVENT_TYPE
{
UNKNOWN, /**< Connect event has not been set yet, not a valid value. */
USER_OFFLINE_AVAILABLE, /**< User logged in, and credentials from offline store are loaded. */
USER_ACCESS_TOKEN_OK, /**< (Not available) dz_connect_login_with_email() ok, and access_token is available */
USER_ACCESS_TOKEN_FAILED, /**< (Not available) dz_connect_login_with_email() failed */
USER_LOGIN_OK, /**< Login with access_token ok, infos from user available. */
USER_LOGIN_FAIL_NETWORK_ERROR, /**< Login with access_token failed because of network condition. */
USER_LOGIN_FAIL_BAD_CREDENTIALS, /**< Login with access_token failed because of bad credentials. */
USER_LOGIN_FAIL_USER_INFO, /**< Login with access_token failed because of other problem. */
USER_LOGIN_FAIL_OFFLINE_MODE, /**< Login with access_token failed because we are in forced offline mode. */
USER_NEW_OPTIONS, /**< User options have just changed. */
ADVERTISEMENT_START, /**< A new advertisement needs to be displayed. */
ADVERTISEMENT_STOP, /**< An advertisement needs to be stopped. */
};
public enum PLAYER_COMMANDS
{
UNKNOWN, /**< Player command has not been set yet, not a valid value. */
START_TRACKLIST, /**< A new tracklist was loaded and a track played. */
JUMP_IN_TRACKLIST, /**< The user jump into a new song in the current tracklist. */
NEXT, /**< Next button. */
PREV, /**< Prev button. */
DISLIKE, /**< Dislike button. */
NATURAL_END, /**< Natural end. */
RESUMED_AFTER_ADS, /**< Reload after playing an ads. */
}
public enum TRACKLIST_AUTOPLAY_MODE
{
MODE_UNKNOWN,
MANUAL,
MODE_ONE,
MODE_ONE_REPEAT,
MODE_NEXT,
MODE_NEXT_REPEAT,
MODE_RANDOM,
MODE_RANDOM_REPEAT,
};
public enum PLAYER_EVENT_TYPE
{
UNKNOWN, /**< Player event has not been set yet, not a valid value. */
// Data access related event.
LIMITATION_FORCED_PAUSE, /**< Another deezer player session was created elsewhere, the player has entered pause mode. */
// Track selection related event.
PLAYLIST_TRACK_NOT_AVAILABLE_OFFLINE,/**< You're offline, the track is not available. */
PLAYLIST_TRACK_NO_RIGHT, /**< You don't have the right to render this track. */
PLAYLIST_TRACK_RIGHTS_AFTER_AUDIOADS,/**< You have right to play it, but you should render an ads first :
- Use dz_player_event_get_advertisement_infos_json().
- Play an ad with dz_player_play_audioads().
- Wait for #DZ_PLAYER_EVENT_RENDER_TRACK_END.
- Use dz_player_play() with previous track or DZ_PLAYER_PLAY_CMD_RESUMED_AFTER_ADS (to be done even on radios for now).
*/
PLAYLIST_SKIP_NO_RIGHT, /**< You're on a radio, and you had no right to do skip. */
PLAYLIST_TRACK_SELECTED, /**< A track is selected among the ones available on the server, and will be fetched and read. */
PLAYLIST_NEED_NATURAL_NEXT, /**< We need a new natural_next action. */
// Data loading related event.
MEDIASTREAM_DATA_READY, /**< Data is ready to be introduced into audio output (first data after a play). */
MEDIASTREAM_DATA_READY_AFTER_SEEK, /**< Data is ready to be introduced into audio output (first data after a seek). */
// Play (audio rendering on output) related event.
RENDER_TRACK_START_FAILURE, /**< Error, track is unable to play. */
RENDER_TRACK_START, /**< A track has started to play. */
RENDER_TRACK_END, /**< A track has stopped because the stream has ended. */
RENDER_TRACK_PAUSED, /**< Currently on paused. */
RENDER_TRACK_SEEKING, /**< Waiting for new data on seek. */
RENDER_TRACK_UNDERFLOW, /**< Underflow happened whilst playing a track. */
RENDER_TRACK_RESUMED, /**< Player resumed play after a underflow or a pause. */
RENDER_TRACK_REMOVED, /**< Player stopped playing a track. */
};
#endregion
#region Delegates
// called with userdata Dispatcher on connect events
public delegate void ConnectOnEventCb(Connect connect, ConnectEvent connectEvent, DispatcherObject userdata);
public delegate void PlayerOnEventCb(Player player, PlayerEvent playerEvent, DispatcherObject userdata);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
unsafe public delegate void libcConnectOnEventCb(CONNECT* libcConnect, CONNECT_EVENT* libcConnectEvent, IntPtr userdata);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
unsafe public delegate bool libcAppCrashDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
unsafe public delegate void libcPlayerOnEventCb(PLAYER* libcPlayer, PLAYER_EVENT* libcPlayerEvent, IntPtr userdata);
#endregion
#region Structures
unsafe public struct CONNECT_EVENT { };
unsafe public struct UTF8STRING { };
unsafe public struct CONNECT { };
unsafe public struct PLAYER_EVENT { };
unsafe public struct PLAYER { };
#endregion
#region Imports
#endregion
// to be in sync with dz_connect_configuration
[StructLayout(LayoutKind.Sequential)]
public class ConnectConfig
{
public string ccAppId;
public string ccAppSecret;
public string ccUserProfilePath;
public Dispatcher ccConnectUserdata;
public ConnectOnEventCb ccConnectEventCb;
}
public class ConnectEvent
{
internal CONNECT_EVENT_TYPE eventType;
/* two design strategies:
* - we could keep a reference to CONNECT_EVENT* with dz_object_retain and call method on the fly
* - we extract all info in constructor and have pure managed object
*
* here we keep the second option, because we have to have a managed object anyway, and it's
* a lot fewer unsafe method to expose, even though it's making a lot of calls in the constructor..
*/
public unsafe static ConnectEvent newFromLibcEvent(CONNECT_EVENT* libcConnectEventHndl)
{
CONNECT_EVENT_TYPE eventType;
unsafe
{
eventType = dz_connect_event_get_type(libcConnectEventHndl);
}
switch (eventType)
{
case CONNECT_EVENT_TYPE.USER_ACCESS_TOKEN_OK:
string accessToken;
unsafe
{
IntPtr libcAccessTokenString = dz_connect_event_get_access_token(libcConnectEventHndl);
accessToken = Marshal.PtrToStringAnsi(libcAccessTokenString);
}
return new NewAccessTokenConnectEvent(accessToken);
default:
return new ConnectEvent(eventType);
}
}
public ConnectEvent(CONNECT_EVENT_TYPE eventType)
{
this.eventType = eventType;
}
public CONNECT_EVENT_TYPE GetEventType()
{
return eventType;
}
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe CONNECT_EVENT_TYPE dz_connect_event_get_type(CONNECT_EVENT* dzConnectEvent);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe IntPtr dz_connect_event_get_access_token(CONNECT_EVENT* dzConnectEvent);
}
public class NewAccessTokenConnectEvent : ConnectEvent
{
string accessToken;
public NewAccessTokenConnectEvent(string accessToken)
: base(CONNECT_EVENT_TYPE.USER_ACCESS_TOKEN_OK)
{
this.accessToken = accessToken;
}
public string GetAccessToken()
{
return accessToken;
}
}
unsafe public class Connect
{
// hash
static Hashtable refKeeper = new Hashtable();
internal unsafe CONNECT* libcConnectHndl;
internal ConnectConfig connectConfig;
public unsafe Connect(ConnectConfig cc)
{
NativeMethods.LoadClass();
//ConsoleHelper.AllocConsole();
// attach a console to parent process (launch from cmd.exe)
//ConsoleHelper.AttachConsole(-1);
CONNECT_CONFIG libcCc = new CONNECT_CONFIG();
connectConfig = cc;
IntPtr intptr = new IntPtr(this.GetHashCode());
refKeeper[intptr] = this;
libcCc.ccAppId = cc.ccAppId;
//libcCc.ccAppSecret = cc.ccAppSecret;
libcCc.ccUserProfilePath = UTF8Marshaler.GetInstance(null).MarshalManagedToNative(cc.ccUserProfilePath);
libcCc.ccConnectEventCb = delegate (CONNECT* libcConnect, CONNECT_EVENT* libcConnectEvent, IntPtr userdata)
{
Connect connect = (Connect)refKeeper[userdata];
ConnectEvent connectEvent = ConnectEvent.newFromLibcEvent(libcConnectEvent);
Dispatcher dispather = connect.connectConfig.ccConnectUserdata;
dispather.Invoke(connect.connectConfig.ccConnectEventCb, connect, connectEvent, connect.connectConfig.ccConnectUserdata);
};
libcConnectHndl = dz_connect_new(libcCc);
UTF8Marshaler.GetInstance(null).CleanUpNativeData(libcCc.ccUserProfilePath);
}
public int Start()
{
int ret;
ret = dz_connect_activate(libcConnectHndl, new IntPtr(this.GetHashCode()));
return ret;
}
public string DeviceId()
{
IntPtr libcDeviceId = dz_connect_get_device_id(libcConnectHndl);
if (libcDeviceId == null)
{
return null;
}
return Marshal.PtrToStringAnsi(libcDeviceId);
}
public int SetAccessToken(string accessToken)
{
int ret;
ret = dz_connect_set_access_token(libcConnectHndl, IntPtr.Zero, IntPtr.Zero, accessToken);
return ret;
}
public int SetSmartCache(string path, int quotaKb)
{
int ret;
ret = dz_connect_cache_path_set(libcConnectHndl, IntPtr.Zero, IntPtr.Zero, path);
ret = dz_connect_smartcache_quota_set(libcConnectHndl, IntPtr.Zero, IntPtr.Zero, quotaKb);
return ret;
}
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe CONNECT* dz_connect_new(
[In, MarshalAs(UnmanagedType.LPStruct)]
CONNECT_CONFIG lpcc);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe IntPtr dz_connect_get_device_id(
CONNECT* dzConnect);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_connect_activate(
CONNECT* dzConnect, IntPtr userdata);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_connect_set_access_token(
CONNECT* dzConnect, IntPtr cb, IntPtr userdata, string access_token);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_connect_cache_path_set(
CONNECT* dzConnect, IntPtr cb, IntPtr userdata,
[MarshalAs(UnmanagedType.CustomMarshaler,
MarshalTypeRef=typeof(UTF8Marshaler))]
string local_path);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_connect_smartcache_quota_set(
CONNECT* dzConnect, IntPtr cb, IntPtr userdata,
int quota_kB);
}
public class PlayerEvent
{
internal PLAYER_EVENT_TYPE eventType;
/* two design strategies:
* - we could keep a reference to PLAYER_EVENT* with dz_object_retain and call method on the fly
* - we extract all info in constructor and have pure managed object
*
* here we keep the second option, because we have to have a managed object anyway, and it's
* a lot fewer unsafe method to expose, even though it's making a lot of calls in the constructor..
*/
public unsafe static PlayerEvent newFromLibcEvent(PLAYER_EVENT* libcPlayerEventHndl)
{
PLAYER_EVENT_TYPE eventType;
unsafe
{
eventType = dz_player_event_get_type(libcPlayerEventHndl);
}
switch (eventType)
{
default:
return new PlayerEvent(eventType);
}
}
public PlayerEvent(PLAYER_EVENT_TYPE eventType)
{
this.eventType = eventType;
}
public PLAYER_EVENT_TYPE GetEventType()
{
return eventType;
}
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe PLAYER_EVENT_TYPE dz_player_event_get_type(PLAYER_EVENT* dzPlayerEvent);
}
unsafe public class Player
{
// hash
static Hashtable refKeeper = new Hashtable();
internal unsafe PLAYER* libcPlayerHndl;
internal Connect connect;
internal libcPlayerOnEventCb eventcb;
public unsafe Player(Connect connect, object observer)
{
IntPtr intptr = new IntPtr(this.GetHashCode());
refKeeper[intptr] = this;
libcPlayerHndl = dz_player_new(connect.libcConnectHndl);
this.connect = connect;
}
public int Start(PlayerOnEventCb eventcb)
{
int ret;
ret = dz_player_activate(libcPlayerHndl, new IntPtr(this.GetHashCode()));
this.eventcb = delegate (PLAYER* libcPlayer, PLAYER_EVENT* libcPlayerEvent, IntPtr userdata)
{
Player player = (Player)refKeeper[userdata];
PlayerEvent playerEvent = PlayerEvent.newFromLibcEvent(libcPlayerEvent);
Dispatcher dispather = player.connect.connectConfig.ccConnectUserdata;
dispather.Invoke(eventcb, player, playerEvent, connect.connectConfig.ccConnectUserdata);
};
ret = dz_player_set_event_cb(libcPlayerHndl, this.eventcb);
return ret;
}
public int LoadStream(string url)
{
int ret;
ret = dz_player_load(libcPlayerHndl, IntPtr.Zero, IntPtr.Zero, url);
return ret;
}
public int Play(int idx, PLAYER_COMMANDS cmd)
{
int ret;
ret = dz_player_play(libcPlayerHndl, IntPtr.Zero, IntPtr.Zero, cmd, TRACKLIST_AUTOPLAY_MODE.MODE_ONE, idx);
return ret;
}
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe PLAYER* dz_player_new(CONNECT* lpcc);
//static extern unsafe PLAYER* dz_player_new(CONNECT* lpcc, IntPtr userdata);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_player_set_event_cb(PLAYER* lpcc, libcPlayerOnEventCb cb);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_player_activate(PLAYER* dzPlayer, IntPtr userdata);
//static extern unsafe int dz_player_activate(PLAYER* dzPlayer, IntPtr userdata);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_player_load(PLAYER* dzPlayer, IntPtr cb, IntPtr userdata, string url);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_player_play(PLAYER* dzPlayer, IntPtr cb, IntPtr userdata, PLAYER_COMMANDS cmd, TRACKLIST_AUTOPLAY_MODE mode, int idx);
//static extern unsafe int dz_player_play(PLAYER* dzPlayer, IntPtr cb, IntPtr userdata, int idx, TRACKLIST_AUTOPLAY_MODE mode);
}
[StructLayout(LayoutKind.Sequential)]
public class CONNECT_CONFIG
{
public string ccAppId;
public string ccProductId;
public string ccProductBuildId;
public IntPtr ccUserProfilePath;
public libcConnectOnEventCb ccConnectEventCb;
public string ccAnonymousBlob;
public libcAppCrashDelegate ccAppCrashDelegate;
}
// trick from http://stackoverflow.com/questions/1573724/cpu-architecture-independent-p-invoke-can-the-dllname-or-path-be-dynamic
// but actually SetDllDirectory works better (for pthread.dll)
public static class NativeMethods
{
// call this to load this class
public static void LoadClass()
{
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetDllDirectory(string lpPathName);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr LoadLibrary(string lpFileName);
static NativeMethods()
{
string arch;
string basePath = System.IO.Path.GetDirectoryName(typeof(NativeMethods).Assembly.Location);
if (IntPtr.Size == 4)
arch = "i386";
else
arch = "x86_64";
System.Diagnostics.Debug.WriteLine("using arch: " + arch);
SetDllDirectory(System.IO.Path.Combine(basePath, arch));
#if false // can be used to debug library loading
IntPtr hExe = LoadLibrary("libdeezer.x64.dll");
if (hExe == IntPtr.Zero)
{
Win32Exception ex = new Win32Exception(Marshal.GetLastWin32Error());
System.Console.WriteLine("exception:" + ex);
throw ex;
}
#endif
}
}
// http://stackoverflow.com/questions/10415807/output-console-writeline-from-wpf-windows-applications-to-actual-console
public class ConsoleHelper
{
/// <summary>
/// Allocates a new console for current process.
/// </summary>
[DllImport("kernel32.dll")]
public static extern Boolean AllocConsole();
[DllImport("Kernel32.dll")]
public static extern bool AttachConsole(int processId);
/// <summary>
/// Frees the console.
/// </summary>
[DllImport("kernel32.dll")]
public static extern Boolean FreeConsole();
}
// http://www.codeproject.com/Articles/138614/Advanced-Topics-in-PInvoke-String-Marshaling
public class UTF8Marshaler : ICustomMarshaler
{
static UTF8Marshaler static_instance;
// maybe we could play with WideCharToMultiByte too and avoid Marshal.Copy
// http://stackoverflow.com/questions/537573/how-to-get-intptr-from-byte-in-c-sharp
/*
Byte[] byNewData = null;
iNewDataLen = NativeMethods.WideCharToMultiByte(NativeMethods.CP_UTF8, 0, cc.ccUserProfilePath, -1, null, 0, IntPtr.Zero, IntPtr.Zero);
Console.WriteLine("iNewDataLen:" + iNewDataLen + " len:" + cc.ccUserProfilePath.Length + " ulen:" + iNewDataLen);
byNewData = new Byte[iNewDataLen];
iNewDataLen = NativeMethods.WideCharToMultiByte(NativeMethods.CP_UTF8, 0, cc.ccUserProfilePath, cc.ccUserProfilePath.Length, byNewData, iNewDataLen, IntPtr.Zero, IntPtr.Zero);
libcCc.ccUserProfilePath = Marshal.UnsafeAddrOfPinnedArrayElement(byNewData, 0);
*/
public IntPtr MarshalManagedToNative(object managedObj)
{
if (managedObj == null)
return IntPtr.Zero;
if (!(managedObj is string))
throw new MarshalDirectiveException(
"UTF8Marshaler must be used on a string.");
// not null terminated
byte[] strbuf = System.Text.Encoding.UTF8.GetBytes((string)managedObj);
IntPtr buffer = Marshal.AllocHGlobal(strbuf.Length + 1);
Marshal.Copy(strbuf, 0, buffer, strbuf.Length);
// write the terminating null
Marshal.WriteByte(buffer + strbuf.Length, 0);
return buffer;
}
public unsafe object MarshalNativeToManaged(IntPtr pNativeData)
{
byte* walk = (byte*)pNativeData;
// find the end of the string
while (*walk != 0)
{
walk++;
}
int length = (int)(walk - (byte*)pNativeData);
// should not be null terminated
byte[] strbuf = new byte[length];
// skip the trailing null
Marshal.Copy((IntPtr)pNativeData, strbuf, 0, length);
string data = System.Text.Encoding.UTF8.GetString(strbuf);
return data;
}
public void CleanUpNativeData(IntPtr pNativeData)
{
Marshal.FreeHGlobal(pNativeData);
}
public void CleanUpManagedData(object managedObj)
{
}
public int GetNativeDataSize()
{
return -1;
}
public static ICustomMarshaler GetInstance(string cookie)
{
if (static_instance == null)
{
return static_instance = new UTF8Marshaler();
}
return static_instance;
}
[DllImport("kernel32.dll")]
public static extern int WideCharToMultiByte(uint CodePage, uint dwFlags,
[MarshalAs(UnmanagedType.LPWStr)] string lpWideCharStr, int cchWideChar,
[MarshalAs(UnmanagedType.LPArray)] Byte[] lpMultiByteStr, int cbMultiByte, IntPtr lpDefaultChar,
IntPtr lpUsedDefaultChar);
public const uint CP_UTF8 = 65001;
}
}
And here is the caller method to play a song :
ConnectConfig dConfig = new ConnectConfig();
dConfig.ccAppId = appId;
dConfig.ccAppSecret = appSecret;
dConfig.ccConnectUserdata = this.Dispatcher;
dConfig.ccUserProfilePath = #"D:\devlocal\dztemp";
Connect dConnect = new Connect(dConfig);
dConnect.SetAccessToken(accesToken);
//dConnect.SetSmartCache(#"D:\devlocal\dztemp", 2000000);
CONNECT_EVENT_TYPE resp = (CONNECT_EVENT_TYPE)dConnect.Start();
String devId = dConnect.DeviceId();
Object dObserver = null;
Player dPlayer = new Player(dConnect, dObserver);
PLAYER_EVENT_TYPE respPlayerStart = (PLAYER_EVENT_TYPE)dPlayer.Start(dPlayerOnEventCb);
PLAYER_EVENT_TYPE respPlayerLoad = (PLAYER_EVENT_TYPE)dPlayer.LoadStream("dzmedia:///track/97206076");
PLAYER_EVENT_TYPE respPlayerPlay = (PLAYER_EVENT_TYPE)dPlayer.Play(0, PLAYER_COMMANDS.NEXT);
First lines seem to be working correctly but:
CONNECT_EVENT_TYPE resp = (CONNECT_EVENT_TYPE)dConnect.Start();
returns a bad value (always the first one of the enum).
String devId = dConnect.DeviceId();
is ok, I have my device token.
dPlayer.Start, dPlayer.LoadStream, dPlayer.Play are returning bad values and there is no sound playing.
You are miscasting the returned value. Most of the functions return dz_error_t (you can find the enum values in deezer-object.h) the enum CONNECT_EVENT_TYPE is mapped on dz_connect_event_t.
I would suggest to create a new enum mapped on dz_error_t and cast with this new enum.
Moreover, as described in http://developers.deezer.com/sdk/native (Section Playing a song) for the moment only DZ_TRACKLIST_AUTOPLAY_MANUAL is supported. So I would suggest to change the dz_player_play call into:
dz_player_play(libcPlayerHndl, IntPtr.Zero, IntPtr.Zero, cmd, TRACKLIST_AUTOPLAY_MODE.MANUAL, idx);
and dPlayer.Play into:
dPlayer.Play(0, PLAYER_COMMANDS.START_TRACKLIST);
If you still have an issue, please also post the traces returned by your application.
Do you have files created in the path provided by ccUserProfilePath ?
If you still have an issue, be sure you have right to play the song by trying to play it with your Internet browser at this address http://www.deezer.com/track/97206076.
Regards,
Cyril

C# method's type signature is not pinvoke compatible

I want to use API from Omron V4KU, the documentation described like this :
Original c# code :
const string DllLocation = #"..\..\Libs\Omron\OMCR.dll";
[DllImport(DllLocation)]
public static extern LPCOMCR OMCR_OpenDevice(string lpcszDevice, LPCOMCR_OPTION lpcOption);
public void Start()
{
var lpcOption = new LPCOMCR_OPTION();
var result = OMCR_OpenDevice(null, lpcOption); // error method's type signature is not pinvoke compatible
}
[StructLayout(LayoutKind.Sequential)]
public struct LPCOMCR
{
public string lpcszDevice;
public IntPtr hDevice;
public uint lpcDevice;
}
[StructLayout(LayoutKind.Sequential)]
public struct LPCOMCR_OPTION
{
public uint dwReserved0;
public uint dwReserved1;
public uint dwReserved2;
public uint dwReserved3;
}
if I missed or wrong in writing code?
sorry, my english is bad. thanks for help.
Start by defining the union structure correctly:
// OMCR_OPTION.COM
[StructLayout(LayoutKind.Sequential)]
public struct OmcrCom
{
public IntPtr Reserved0;
public uint BaudRate;
public uint Reserved1;
public uint Reserved2;
public uint Reserved3;
public IntPtr Reserved1;
public IntPtr Reserved2;
}
// OMCR_OPTION.USB
[StructLayout(LayoutKind.Sequential)]
public struct OmcrUsb
{
public uint Reserved0;
public uint Reserved1;
public uint Reserved2;
public uint Reserved3;
}
// OMCR_OPTION (union of COM and USB)
[StructLayout(LayoutKind.Explicit)]
public struct OmcrOptions
{
[FieldOffset(0)]
public OmcrCom Com;
[FieldOffset(0)]
public OmcrUsb Usb;
}
// OMCR
[StructLayout(LayoutKind.Sequential)]
public struct OmcrDevice
{
public string Device;
public IntPtr DeviceHandle;
public IntPtr DevicePointer;
}
[DllImport(dllName: DllLocation, EntryPoint = "OMCR_OpenDevice"]
public static extern IntPtr OmcrOpenDevice(string type, ref OmcrOptions options);
And then call the method, something like:
var options = new OmcrOptions();
options.Com.BaudRate = 115200; // or whatever you need to set
var type = "COM"; // is this USB/COM? not sure
OmcrDevice device;
var devicePtr = OmcrOpenDevice(type, ref options);
if (devicePtr == IntPtr.Zero)
device = (OmcrDevice)Marshal.PtrToStructure(devicePtr, typeof(OmcrDevice));
Well, for one, the documentation is asking you to pass LPCOMCR_OPTION as a pointer - you're passing it as a value. Using ref should help. There's another problem, though, and that's the return value - again, you're trying to interpret it as a value, while the docs say it's a pointer. However, this is a lot trickier than the first error - as far as I'm aware, your only options are using a C++/CLI interop library, or expecting IntPtr as a return value. In any case, you need to handle proper deallocation of the memory you get this way.
You need to do it like this:
[StructLayout(LayoutKind.Sequential)]
public struct OMCR
{
[MarshalAs(UnmanagedType.LPStr)]
public string lpcszDevice;
public IntPtr hDevice;
public IntPtr lpcDevice;
}
[StructLayout(LayoutKind.Sequential)]
public struct OMCR_OPTION
{
public uint dwReserved0;
public uint dwReserved1;
public uint dwReserved2;
public uint dwReserved3;
}
[DllImport(DllLocation, CallingConvention = CallingConvention.???,
SetLastError = true)]
public static extern IntPtr OMCR_OpenDevice(string lpcszDevice,
ref OMCR_OPTION lpcOption);
You need to replace CallingConvention.??? with the appropriate calling convention. We cannot tell from the question what that is. You will have to find out by reading the header file.
The return value is a pointer to OMCR. You need to hold on to this pointer and pass it to OMCR_CloseDevice when you are finished with it.
In order to obtain an OMCR value you would do the following:
OMCR_OPTION Option = new OMCR_OPTION(); // not sure how to initialize this
IntPtr DevicePtr = OMCR_OpenDevice(DeviceType, ref Option);
if (DevicePtr == IntPtr.Zero)
throw new Win32Exception();
OMCR Device = (OMCR)Marshal.PtrToStructure(DevicePtr, typeof(OMCR));

P/Invoke WlanHostedNetworkQueryStatus from wlanapi.dll

I have mapped C++ function (from WLanapi.dll):
DWORD WINAPI WlanHostedNetworkQueryStatus(
_In_ HANDLE hClientHandle,
_Out_ PWLAN_HOSTED_NETWORK_STATUS *ppWlanHostedNetworkStatus,
_Reserved_ PVOID pvReserved
);
To the following C# code:
[DllImport("Wlanapi.dll", SetLastError = true)]
static extern UInt32 WlanHostedNetworkQueryStatus(
[In] IntPtr hClientHandle,
[Out] out _WLAN_HOSTED_NETWORK_STATUS ppWlanHostedNetworkStatus,
[In, Out] IntPtr pvReserved
);
I have also mapped all the structs and enums required and other stuff (for example to get the clientHandle pointer and to start the hosted network).
The _WLAN_HOSTED_NETWORK_STATUS is mapped like so:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _WLAN_HOSTED_NETWORK_STATUS
{
public _WLAN_HOSTED_NETWORK_STATE HostedNetworkState;
public Guid IPDeviceID;
public _DOT11_MAC_ADDRESS wlanHostedNetworkBSSID;
public _DOT11_PHY_TYPE dot11PhyType;
public UInt32 ulChannelFrequency;
public UInt32 dwNumberOfPeers;
public _WLAN_HOSTED_NETWORK_PEER_STATE[] PeerList;
}
Now when executing that function, I am not sure how to use ppWlanHostedNetworkStatus correctly and such. The function returns ERROR_SUCCESS (0) which means I have called it and passed parameters correctly as far as I am concerned:
_WLAN_HOSTED_NETWORK_STATUS netStatus = new _WLAN_HOSTED_NETWORK_STATUS();
WlanHostedNetworkQueryStatus(clientHandle, out netStatus, IntPtr.Zero);
But while querying ppWlanHostedNetworkStatus for values (like state of the network, or number of connected peers) I am getting just some strange long integers (I would say memory addresses, but I am not sure), for example call:
netStatus.HostedNetworkState.ToString();
Returns
11465720
HostedNetworkState is an enumeration defined like:
public enum _WLAN_HOSTED_NETWORK_STATE
{
wlan_hosted_network_unavailable,
wlan_hosted_network_idle,
wlan_hosted_network_active
}
So .toString() should have returned one of these strings from the enumeration, right?
I am pretty sure it is something to do with pointers etc, since in the documentation of the _WLAN_HOSTED_NETWORK_STATUS ( MS documentation ) it says that before the call to that function, the ppWlanHostedNetworkStatus should be a NULL, and that it is itself a pointer to the structure...
How can I debug it? I am coding in C#, VS 2012...
Thanks for your help.
-----EDIT-----
I further tried to map the function with IntPtr as an argument, pass IntPtr.Zero and Marshal.PtrToStruct, but I am getting AccessViolationException when trying to do that...
[DllImport("Wlanapi.dll", SetLastError = true)]
static extern UInt32 WlanHostedNetworkQueryStatus(
[In] IntPtr hClientHandle,
[Out] out IntPtr ppWlanHostedNetworkStatus,
[In, Out] IntPtr pvReserved
);
And then:
IntPtr ppStatus = IntPtr.Zero;
WlanHostedNetworkQueryStatus(clientHandle, out ppStatus, IntPtr.Zero);
_WLAN_HOSTED_NETWORK_STATUS netStatus = (_WLAN_HOSTED_NETWORK_STATUS)Marshal.PtrToStructure(ppStatus, typeof(_WLAN_HOSTED_NETWORK_STATUS));
------EDIT 2-------
Following advice from Fermat2357, I have uncommented part of the struct to map, and change the following to count for a pointer to pointer:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _WLAN_HOSTED_NETWORK_STATUS
{
public _WLAN_HOSTED_NETWORK_STATE HostedNetworkState;
public Guid IPDeviceID;
public _DOT11_MAC_ADDRESS wlanHostedNetworkBSSID;
public _DOT11_PHY_TYPE dot11PhyType;
public UInt32 ulChannelFrequency;
public UInt32 dwNumberOfPeers;
//public _WLAN_HOSTED_NETWORK_PEER_STATE[] PeerList;
}
I call it like this:
IntPtr ppStatus = IntPtr.Zero;
WlanHostedNetworkQueryStatus(clientHandle, out ppStatus, IntPtr.Zero);
IntPtr ppStatus2 = new IntPtr(ppStatus.ToInt32());
_WLAN_HOSTED_NETWORK_STATUS stat = (_WLAN_HOSTED_NETWORK_STATUS)Marshal.PtrToStructure(ppStatus2, typeof(_WLAN_HOSTED_NETWORK_STATUS));
netStatus = stat.HostedNetworkState.ToString();
This finally gives me the correct network status (active after starting it)... Now I have to find a way to marshal that dynamic array...
Thanks so far for help Fermat2357
Your mapping is incorrect. Take a look at the definition of the API function WlanHostedNetworkQueryStatus.
DWORD WINAPI WlanHostedNetworkQueryStatus(
_In_ HANDLE hClientHandle,
_Out_ PWLAN_HOSTED_NETWORK_STATUS *ppWlanHostedNetworkStatus,
_Reserved_ PVOID pvReserved
);
Please take care, the parameter ppWlanHostedNetworkStatus is a pointer to a pointer to a WLAN_HOSTED_NETWORK_STATUS structure.
Take a deeper look in the documentation of the function you will find
ppWlanHostedNetworkStatus [out]
On input, this parameter must be NULL.
On output, this parameter receives a pointer to the current status of the wireless Hosted Network, if the call to the WlanHostedNetworkQueryStatus function succeeds. The current status is returned in a WLAN_HOSTED_NETWORK_STATUS structure.
If you give in a NULL pointer (as described in the documentation) the underlying API will allocate a buffer for you holding the structure and initializing the pointer to this buffer. Later dont forget to free it by a call to WlanFreeMemory. Otherwise you will have a resource leak here.
However the documentation seems to be not 100% complete for this function. In my tests (Win7 32Bit) the API will not allocate memory for you if you intitalize the pointer to a enough big memory buffer. In this case a later call to WlanFreeMemory seems not be necessary. But in this case its hard to guess how much memory you need for the following WLAN_HOSTED_NETWORK_PEER_STATE structures. For that reason this seems not to be useable anyway.
Here is the C code I used for testing
#include <Wlanapi.h>
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwRes;
HANDLE hHandle;
DWORD negotiatedVersion;
dwRes = WlanOpenHandle(1, NULL, &negotiatedVersion, &hHandle);
if (ERROR_SUCCESS == dwRes)
{
PWLAN_HOSTED_NETWORK_STATUS pStatus = NULL;
dwRes = WlanHostedNetworkQueryStatus(hHandle, &pStatus, NULL);
if (ERROR_SUCCESS == dwRes)
{
if (wlan_hosted_network_unavailable != pStatus->HostedNetworkState)
{
// Do something with the result
}
WlanFreeMemory(pStatus);
}
else
{
// handle Error
}
WlanCloseHandle(hHandle, NULL);
}
else
{
// handle Error
}
return 0;
}
If you want to make your sample work you need to modify the way you marshal the structure.
Edit
To marshal correctly you can try the following:
...
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _WLAN_HOSTED_NETWORK_STATUS
{
public _WLAN_HOSTED_NETWORK_STATE HostedNetworkState;
public Guid IPDeviceID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
public string wlanHostedNetworkBSSID;
public _DOT11_PHY_TYPE dot11PhyType;
public UInt32 ulChannelFrequency;
public UInt32 dwNumberOfPeers;
public _WLAN_HOSTED_NETWORK_PEER_STATE PeerList;
}
...
IntPtr ptr = new IntPtr();
uint hostedNetworkQueryStatusSuccess = WlanHostedNetworkQueryStatus(clientHandle, out ptr, IntPtr.Zero);
if (openHandleSuccess == 0)
{
var netStat = (_WLAN_HOSTED_NETWORK_STATUS)Marshal.PtrToStructure(ptr, typeof(_WLAN_HOSTED_NETWORK_STATUS));
Console.WriteLine(netStat.HostedNetworkState);
if (netStat.HostedNetworkState != _WLAN_HOSTED_NETWORK_STATE.wlan_hosted_network_unavailable)
{
IntPtr offset = Marshal.OffsetOf(typeof(_WLAN_HOSTED_NETWORK_STATUS), "PeerList");
for (int i = 0; i < netStat.dwNumberOfPeers; i++)
{
var peer = (_WLAN_HOSTED_NETWORK_PEER_STATE)Marshal.PtrToStructure(
new IntPtr(ptr.ToInt64() + offset.ToInt64()),
typeof(_WLAN_HOSTED_NETWORK_PEER_STATE));
System.Console.WriteLine(peer.PeerMacAddress);
offset += Marshal.SizeOf(peer);
}
}
}
The Idea: Im using a structure here that I can be sure it is marshaled correctly all the time. Later with the knowledge of the dwNumberOfPeers member (if it is valid) I get all the inner structures step by step.
EDIT: One thing to try is a trick I use to help debugging - replace your struct with a byte array, and see what you pull back (ok, in this case a uint array):
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _WLAN_HOSTED_NETWORK_STATUS
{
[MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U4, SizeConst = 48)]
public uint[] scratch;
}
I get very strange values when I run this locally, however - the first (10x4) = 40 bytes are zeroed out:
0 0 0 0
0 0 0 0
0 0 4D454D4C 28
16DCD870 0 787F6447 80000020
...omitted...
Try this set of P/Invoke declarations:
(done in LINQPad, so replace "Dump" method accordingly)
void Main()
{
IntPtr clientHandle;
int negotiatedVersion;
if(WlanOpenHandle(2, IntPtr.Zero, out negotiatedVersion, out clientHandle) != 0)
{
throw new InvalidOperationException("Could not open handle");
}
Console.WriteLine("Negotiated version:{0}", negotiatedVersion);
IntPtr pNetStatus = IntPtr.Zero;
if(WlanHostedNetworkQueryStatus(clientHandle, out pNetStatus, IntPtr.Zero) != 0)
{
throw new InvalidOperationException("Could not query network status");
}
var netStatus = (WLAN_HOSTED_NETWORK_STATUS)Marshal.PtrToStructure(pNetStatus, typeof(WLAN_HOSTED_NETWORK_STATUS));
Console.WriteLine(netStatus.PeerList[0]);
WlanFreeMemory(pNetStatus);
WlanCloseHandle(clientHandle, IntPtr.Zero);
}
[DllImport("Wlanapi.dll", SetLastError = true)]
[return:MarshalAs(UnmanagedType.Bool)]
public static extern bool WlanOpenHandle(
[In] int dwClientVersion,
IntPtr pReserved,
[Out] out int pdwNegotiatedVersion,
[Out] out IntPtr phClientHandle
);
[DllImport("Wlanapi.dll", SetLastError = true)]
[return:MarshalAs(UnmanagedType.Bool)]
public static extern bool WlanCloseHandle(
[In] IntPtr hClientHandle,
IntPtr pReserved
);
[DllImport("Wlanapi.dll", SetLastError = true)]
static extern UInt32 WlanHostedNetworkQueryStatus(
[In] IntPtr hClientHandle,
[Out] out IntPtr ppWlanHostedNetworkStatus,
[In, Out] IntPtr pvReserved
);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _WLAN_HOSTED_NETWORK_STATUS
{
public _WLAN_HOSTED_NETWORK_STATE HostedNetworkState;
public Guid IPDeviceID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=6)]
public string wlanHostedNetworkBSSID;
public _DOT11_PHY_TYPE dot11PhyType;
public UInt32 ulChannelFrequency;
public UInt32 dwNumberOfPeers;
public IntPtr PeerList;
}
public enum _WLAN_HOSTED_NETWORK_STATE
{
wlan_hosted_network_unavailable,
wlan_hosted_network_idle,
wlan_hosted_network_active
}
public enum _DOT11_PHY_TYPE : uint
{
dot11_phy_type_unknown = 0,
dot11_phy_type_any = 0,
dot11_phy_type_fhss = 1,
dot11_phy_type_dsss = 2,
dot11_phy_type_irbaseband = 3,
dot11_phy_type_ofdm = 4,
dot11_phy_type_hrdsss = 5,
dot11_phy_type_erp = 6,
dot11_phy_type_ht = 7,
dot11_phy_type_IHV_start = 0x80000000,
dot11_phy_type_IHV_end = 0xffffffff
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _WLAN_HOSTED_NETWORK_PEER_STATE
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=6)]
public string PeerMacAddress;
_WLAN_HOSTED_NETWORK_PEER_AUTH_STATE PeerAuthState;
}
public enum _WLAN_HOSTED_NETWORK_PEER_AUTH_STATE
{
wlan_hosted_network_peer_state_invalid,
wlan_hosted_network_peer_state_authenticated
}

System.AccessViolationException when calling C++ dll

I am trying to get the Java Access Bridge (2.02) to work with C# (.NET 3.5). I do have it working for some functions, but when I call a function that references a struct(getAccessibleContextInfo), I get this error message:
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Here is my code:
[DllImport("Windowsaccessbridge.dll", CallingConvention = CallingConvention.Cdecl)]
internal extern static void Windows_run();
[DllImport("Windowsaccessbridge.dll", CallingConvention = CallingConvention.Cdecl)]
private extern static void releaseJavaObject(long vmID, IntPtr javaObject);
[DllImport("Windowsaccessbridge.dll", CallingConvention = CallingConvention.Cdecl)]
private extern static bool isJavaWindow(IntPtr window);
[DllImport("Windowsaccessbridge.dll", CallingConvention = CallingConvention.Cdecl)]
private extern static bool getAccessibleContextInfo(long vmID, IntPtr ac, out AccessibleContextInfo textInfo);
[DllImport("Windowsaccessbridge.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern bool getAccessibleContextFromHWND(IntPtr hwnd, out long vmID, out IntPtr ac);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct AccessibleContextInfo
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
public string name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
public string description;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string role;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string role_en_US;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string states;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string states_en_US;
[MarshalAs(UnmanagedType.I4)]
public int indexInParent;
[MarshalAs(UnmanagedType.I4)]
public int childrenCount;
[MarshalAs(UnmanagedType.I4)]
public int x;
[MarshalAs(UnmanagedType.I4)]
public int y;
[MarshalAs(UnmanagedType.I4)]
public int width;
[MarshalAs(UnmanagedType.I4)]
public int height;
[MarshalAs(UnmanagedType.Bool)]
public bool accessibleComponent;
[MarshalAs(UnmanagedType.Bool)]
public bool accessibleAction;
[MarshalAs(UnmanagedType.Bool)]
public bool accessibleSelection;
[MarshalAs(UnmanagedType.Bool)]
public bool accessibleText;
[MarshalAs(UnmanagedType.Bool)]
public bool accessibleInterfaces;
};
private void Form1_Load(object sender, EventArgs e)
{
Windows_run();
}
private void button1_Click(object sender, EventArgs e)
{
long vmID;
IntPtr ac;
if (getAccessibleContextFromHWND(mainWindowHwnd, out vmID, out ac))
{
MessageBox.Show("Got Context: " + vmID.ToString() + ", " + ac.ToString());
AccessibleContextInfo info;
if (getAccessibleContextInfo(vmID, ac, out info)) //this is where the error is thrown
{
MessageBox.Show("Got Context Info: " + info.name);
}
else
{
MessageBox.Show("Getting info failed");
}
}
else
{
MessageBox.Show("Accessing failed");
}
}
I don't think it's a problem with the java dll, as it works fine with the example C++ program that comes with the API.
I'm guessing from searching google that it's a problem with the way I am marshalling the struct AccessibleContextInfo, but I don't have a clue as to how to do it correctly.
Here is the way the struct is declared in the sample program's "AccessBridgePackages.h"
#define MAX_STRING_SIZE 1024
#define SHORT_STRING_SIZE 256
typedef struct AccessibleContextInfoTag {
wchar_t name[MAX_STRING_SIZE]; // the AccessibleName of the object
wchar_t description[MAX_STRING_SIZE]; // the AccessibleDescription of the object
wchar_t role[SHORT_STRING_SIZE]; // localized AccesibleRole string
wchar_t role_en_US[SHORT_STRING_SIZE]; // AccesibleRole string in the en_US locale
wchar_t states[SHORT_STRING_SIZE]; // localized AccesibleStateSet string (comma separated)
wchar_t states_en_US[SHORT_STRING_SIZE]; // AccesibleStateSet string in the en_US locale (comma separated)
jint indexInParent; // index of object in parent
jint childrenCount; // # of children, if any
jint x; // screen coords in pixels
jint y; // "
jint width; // pixel width of object
jint height; // pixel height of object
BOOL accessibleComponent; // flags for various additional
BOOL accessibleAction; // Java Accessibility interfaces
BOOL accessibleSelection; // FALSE if this object doesn't
BOOL accessibleText; // implement the additional interface
// in question
// BOOL accessibleValue; // old BOOL indicating whether AccessibleValue is supported
BOOL accessibleInterfaces; // new bitfield containing additional interface flags
} AccessibleContextInfo;
Any help is much appreciated!
Normally AccessViolations indicate you've messed up the PInvoke signature, but it looks like it is generally OK.
I can think of two things to try that may help
1: Potentially suspicious use of Charset.Unicode at the struct level.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct AccessibleContextInfo
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
public string name;
...
One might expect that the CharSet=Unicode on the struct should "propagate" to it's members, but I've seen situations where it doesn't appear to do this.
You could try specifying CharSet=Unicode on each string's MarshalAs attribute.
I'm not sure if this would make a difference though, given that the default marshalling of strings from .NET is already Unicode, but it may be worth a shot.
2: Perhaps try passing the AccessibleContextInfo struct as a ref parameter rather than an out parameter - eg
[DllImport("Windowsaccessbridge.dll", CallingConvention = CallingConvention.Cdecl)]
private extern static bool getAccessibleContextInfo(long vmID, IntPtr ac, ref AccessibleContextInfo textInfo);
Update: Also, as Hans Passant notes in a comment, remember long in C# is a 64-bit int, whereas in C/C++ it's 32-bit. Depending on the C++ function declarations, that may be wrong

Passing a struct pointer as a parameter in C#

I have a function in C++ that I exported to a DLL. I contains a struct pointer as one of the parameters. I need to use this function in C#, so I used DLLImport for the function and recreated the struct in C# using StructLayout. I've tried passing in the parameter using ref as well as tried Marshaling it in using MarshalAs(UnmangedType.Struct) and Marshal.PtrToStructure. The parameter still isn't passing correctly.
Example:
[DllImport("testdll.dll")]
public static extern int getProduct(int num1, int num2, [MarshalAs(UnmanagedType.Struct)] ref test_packet tester);
One more tidbit of info, the struct contains a byte* var, which I think may be causing the problem in terms of passing the param as ref. Any ideas? Am I on the right track? Thanks for the help.
Thanks nobugz for the response. Here's a sample of the struct def:
//C# DEFINITION
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct test_packet
{
public UInt32 var_alloc_size;
public byte* var;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_TAG_LENGTH)]
public byte[] tag;
}
//ORIGINAL UNMANAGED STRUCT
typedef struct test_packet_tag
{
unsigned int var_alloc_size;
unsigned char *var;
unsigned char tag[MAX_TAG_LENGTH];
} test_packet;
Using "ref" is the correct way, get rid of the [MarshalAs] attribute. The real problem is almost certainly the structure declaration. You didn't post anything that would help us help you with that.
The DllImportAttribute.CharSet property is wrong, make it CharSet.Ansi. The "var" member is declared wrong, make it byte[]. Be sure to initialize it before the call:
var product = new test_packet();
product.var_alloc_size = 666; // Adjust as needed
product.var = new byte[product.var_alloc_size];
int retval = getProduct(42, 43, ref product);
Best guess, hope it works.
Here's an example from my personal stuff. It can be really complicated. Note that its not easy to move arrays over as pointers, so you should look at how one does that in the c# side. This should hit a lot of the major data types. You must make sure your elements line up EXACTLY. Otherwise it will look like its working, but you'll get bad ptrs (at best). I had a great deal of trouble when moving this struct, and this was the only approach that worked. Good luck
Function sig -
[DllImport("stochfitdll.dll", EntryPoint = "Init", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void Init([MarshalAs(UnmanagedType.LPStruct)] ModelSettings settings);
C++ side
#pragma pack(push, 8)
struct ReflSettings
{
LPCWSTR Directory;
double* Q;
double* Refl;
double* ReflError;
double* QError;
int QPoints;
double SubSLD;
double FilmSLD;
double SupSLD;
int Boxes;
double FilmAbs;
double SubAbs;
double SupAbs;
double Wavelength;
BOOL UseSurfAbs;
double Leftoffset;
double QErr;
BOOL Forcenorm;
double Forcesig;
BOOL Debug;
BOOL XRonly;
int Resolution;
double Totallength;
double FilmLength;
BOOL Impnorm;
int Objectivefunction;
double Paramtemp;
LPCWSTR Title;
};
#pragma pack(pop)
C# side -
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)]
public class ModelSettings:IDisposable
{
#region Variables
public string Directory;
public IntPtr Q;
public IntPtr Refl;
public IntPtr ReflError;
public IntPtr QError;
public int QPoints;
public double SubSLD;
public double SurflayerSLD;
public double SupSLD;
public int Boxes;
public double SurflayerAbs;
public double SubAbs;
public double SupAbs;
public double Wavelength;
public bool UseAbs;
public double SupOffset;
public double Percerror;
public bool Forcenorm;
public double Forcesig;
public bool Debug;
public bool ForceXR;
public int Resolution;
public double Totallength;
public double Surflayerlength;
public bool ImpNorm;
public int FitFunc;
public double ParamTemp;
public string version = "0.0.0";
[XmlIgnoreAttribute] private bool disposed = false;
#endregion
public ModelSettings()
{ }
~ModelSettings()
{
Dispose(false);
}
#region Public Methods
public void SetArrays(double[] iQ, double[] iR, double[] iRerr, double[] iQerr)
{
//Blank our arrays if they hold data
if (Q == IntPtr.Zero)
ReleaseMemory();
int size = Marshal.SizeOf(iQ[0]) * iQ.Length;
try
{
QPoints = iQ.Length;
Q = Marshal.AllocHGlobal(size);
Refl = Marshal.AllocHGlobal(size);
ReflError = Marshal.AllocHGlobal(size);
if (iQerr != null)
QError = Marshal.AllocHGlobal(size);
else
QError = IntPtr.Zero;
Marshal.Copy(iQ, 0, Q, iQ.Length);
Marshal.Copy(iR, 0, Refl, iR.Length);
Marshal.Copy(iRerr, 0, ReflError, iRerr.Length);
if (iQerr != null)
Marshal.Copy(iQerr, 0, QError, iQerr.Length);
}
catch (Exception ex)
{
//error handling
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
ReleaseMemory();
// Note disposing has been done.
disposed = true;
}
}
private void ReleaseMemory()
{
if (Q != IntPtr.Zero)
{
Marshal.FreeHGlobal(Q);
Marshal.FreeHGlobal(Refl);
Marshal.FreeHGlobal(ReflError);
if (QError != IntPtr.Zero)
Marshal.FreeHGlobal(QError);
}
}
#endregion
}
Your original P/Invoke declaration should be okay, though you shouldn't need UnmanagedType.Struct there at all. The problem seems to be with your C# struct declaration. In particular, why is the order of the field declarations different from the C++ version?

Categories

Resources