C#deezer native api:适应C#

我正在尝试使用C#类将C ++本机api包装到CLI C#类中。 似乎有一些问题(它确实接近工作)并希望找到问题的一些帮助。

这是包装器的代码

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 { ///  /// Allocates a new console for current process. ///  [DllImport("kernel32.dll")] public static extern Boolean AllocConsole(); [DllImport("Kernel32.dll")] public static extern bool AttachConsole(int processId); ///  /// Frees the console. ///  [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; } } 

以下是播放歌曲的来电方法:

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

第一行似乎正常工作,但:

 CONNECT_EVENT_TYPE resp = (CONNECT_EVENT_TYPE)dConnect.Start(); 

返回一个错误的值(始终是枚举的第一个)。

 String devId = dConnect.DeviceId(); 

没问题,我有我的设备令牌。

dPlayer.StartdPlayer.LoadStreamdPlayer.Play返回错误值,没有声音播放。

你错误地反映了返回的值。 大多数函数返回dz_error_t (您可以在dz_connect_event_t -object.h中找到枚举值)枚举CONNECT_EVENT_TYPE映射到dz_connect_event_t 。 我建议创建一个映射在dz_error_t上的新枚举,然后使用这个新枚举进行dz_error_t

此外,如http://developers.deezer.com/sdk/native ( 播放歌曲部分 )中所述,目前仅支持DZ_TRACKLIST_AUTOPLAY_MANUAL 。 所以我建议将dz_player_play调用更改为:

 dz_player_play(libcPlayerHndl, IntPtr.Zero, IntPtr.Zero, cmd, TRACKLIST_AUTOPLAY_MODE.MANUAL, idx); 

dPlayer.Play成:

 dPlayer.Play(0, PLAYER_COMMANDS.START_TRACKLIST); 

如果您仍有问题,请同时发布您的应用程序返回的痕迹。

您是否在ccUserProfilePath提供的路径中创建了文件?

如果您仍然遇到问题,请确保您有权通过在此地址http://www.deezer.com/track/97206076上使用互联网浏览器播放该歌曲。

问候,

西里尔