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
Related
I'm trying to listen for macOS sleep/wake events using C# .Net Core in Visual Studio for Mac. After searching the forum and various documentation I've written some code that should apparently receive these notifications. Here it is:
using System;
using System.Runtime.InteropServices;
using System.Text;
using CFNotificationCenterRef = System.IntPtr;
namespace NativeMac
{
// I've noticed that some people are using this attribute to decorate their callback method.
[AttributeUsage(AttributeTargets.Method)]
public sealed class MonoPInvokeCallbackAttribute : Attribute
{
public MonoPInvokeCallbackAttribute(Type delegateType)
{
DelegateType = delegateType;
}
public Type DelegateType { get; }
}
// Delegate definition for a notification center callback.
delegate void CFNotificationCallback(CFNotificationCenterRef center, IntPtr observer, IntPtr name, IntPtr obj, IntPtr userInfo);
// Required enum for the call to CFNotificationCenterAddObserver.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1008:Enums should have zero value", Justification = "There is no zero value specified for NotificationSuspensionBehavior.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32", Justification = "Required to be long to work with IntPtr.")]
public enum CFNotificationSuspensionBehavior : long
{
Drop = 1,
Coalesce = 2,
Hold = 3,
DeliverImmediately = 4
}
/// <summary>
/// The intention is for this class to allow us to register for the Mac's native sleep and wake events.
/// </summary>
public static class MacPowerMode
{
private enum CFStringEncoding : uint
{
UTF16 = 0x0100,
UTF16BE = 0x10000100,
UTF16LE = 0x14000100,
ASCII = 0x0600
}
/// <summary>
/// Native methods we can call on the Mac.
/// </summary>
private static class NativeMethods
{
private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation";
private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit";
[DllImport(AppKitFramework, CharSet = CharSet.Ansi)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "objc_getClass method requires CharSet.Ansi to work.")]
public static extern IntPtr objc_getClass(string name);
[DllImport(FoundationFramework, EntryPoint = "objc_msgSend")]
public static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector);
[DllImport(AppKitFramework)]
public static extern IntPtr NSSelectorFromString(IntPtr cfstr);
[DllImport(FoundationFramework)]
public static extern void CFRelease(IntPtr handle);
[DllImport(FoundationFramework)]
public static extern IntPtr CFStringCreateWithBytes(IntPtr allocator, IntPtr buffer, long bufferLength, CFStringEncoding encoding, bool isExternalRepresentation);
[DllImport(FoundationFramework)]
public static extern unsafe void CFNotificationCenterAddObserver(CFNotificationCenterRef center, IntPtr observer,
CFNotificationCallback callback, IntPtr name, IntPtr obj,
CFNotificationSuspensionBehavior suspensionBehavior);
}
public static void Initialise()
{
Console.WriteLine("Init Mac power mode test.");
// Get the appropriate notification centre for system notifications.
var nsWorkspace = NativeMethods.objc_getClass("NSWorkspace");
var sharedWorkspace = NativeMethods.objc_msgSend_retIntPtr(nsWorkspace, GetSelector("sharedWorkspace"));
var notificationCenter = NativeMethods.objc_msgSend_retIntPtr(sharedWorkspace, GetSelector("notificationCenter"));
// Create the names of the notifications that we will be listening for.
var nsWorkspaceWillSleepNotification = CreateCFString("NSWorkspaceWillSleepNotification");
var nsWorkspaceDidWakeNotification = CreateCFString("NSWorkspaceDidWakeNotification");
// Listen out for when the Mac is about to sleep. (Test by selecting 'sleep' from the Apple Menu.)
NativeMethods.CFNotificationCenterAddObserver(center: notificationCenter,
observer: notificationCenter,
callback: NotificationCallbackDelegate,
name: nsWorkspaceWillSleepNotification,
obj: IntPtr.Zero,
suspensionBehavior: CFNotificationSuspensionBehavior.DeliverImmediately);
// Listen out for when the Mac is awoken. (Test by pressing space bar whilst sleeping, to wake Mac.)
NativeMethods.CFNotificationCenterAddObserver(center: notificationCenter,
observer: notificationCenter,
callback: NotificationCallbackDelegate,
name: nsWorkspaceDidWakeNotification,
obj: IntPtr.Zero,
suspensionBehavior: CFNotificationSuspensionBehavior.DeliverImmediately);
}
private static IntPtr GetSelector(string name)
{
IntPtr cfstrSelector = CreateCFString(name);
IntPtr selector = NativeMethods.NSSelectorFromString(cfstrSelector);
NativeMethods.CFRelease(cfstrSelector);
return selector;
}
private static unsafe IntPtr CreateCFString(string aString)
{
var bytes = Encoding.Unicode.GetBytes(aString);
fixed (byte* b = bytes)
{
var cfStr = NativeMethods.CFStringCreateWithBytes(IntPtr.Zero, (IntPtr)b, bytes.Length, CFStringEncoding.UTF16, false);
return cfStr;
}
}
// Delegate definition for a notification center callback.
private delegate void CFNotificationCallback(CFNotificationCenterRef center, IntPtr observer, IntPtr name, IntPtr obj, IntPtr userInfo);
private static CFNotificationCallback NotificationCallbackDelegate = NotificationCallback;
[MonoPInvokeCallback(typeof(CFNotificationCallback))]
private static void NotificationCallback(CFNotificationCenterRef centerPtr, IntPtr observer, IntPtr name, IntPtr obj, IntPtr userInfo)
{
Console.WriteLine($"NotificationCallback received {name}");
}
}
}
There are no errors reported when I call the code, however the callback method never receives a notification for either sleep or wake. I've even tried creating a simple Cocoa app in XCode to see if I can receive the events at all, and that worked fine.
Various links that I've researched:
Apple Documentation Archive: Registering and unregistering for sleep and wake notifications
Apple Developer Documentation: CFNotificationCenter
Xamarin C# Implementation of CFNotificationCenter
What am I doing wrong?
EDIT (11:30 23/09/2020): I've created a static delegate and changed the code above accordingly as per Hans Passant's suggestion in the comments, the code runs but I still do not receive a notification.
I have tried many different combinations of the various methods to marshal this call. This is a DLL that returns a pointer to a pointer to an array of structures. The types like debugPort are actually enums.
/**
* \struct debugConnectParameters
* \brief Get device characterization and specify connection parameters through ST-LINK interface.
*/
typedef struct debugConnectParameters {
debugPort dbgPort; /**< Select the type of debug interface #debugPort. */
int index; /**< Select one of the debug ports connected. */
char serialNumber[33]; /**< ST-LINK serial number. */
char firmwareVersion[20]; /**< Firmware version. */
char targetVoltage[5]; /**< Operate voltage. */
int accessPortNumber; /**< Number of available access port. */
int accessPort; /**< Select access port controller. */
debugConnectMode connectionMode; /**< Select the debug CONNECT mode #debugConnectMode. */
debugResetMode resetMode; /**< Select the debug RESET mode #debugResetMode. */
int isOldFirmware; /**< Check Old ST-LINK firmware version. */
frequencies freq; /**< Supported frequencies #frequencies. */
int frequency; /**< Select specific frequency. */
int isBridge; /**< Indicates if it's Bridge device or not. */
int shared; /**< Select connection type, if it's shared, use ST-LINK Server. */
} debugConnectParameters;
int getStLinkList(debugConnectParameters** stLinkList, int shared);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public unsafe struct debugConnectParameters
{
debugPort dbgPort; /**< Select the type of debug interface #debugPort. */
int index; /**< Select one of the debug ports connected. */
fixed char serialNumber[33]; /**< ST-LINK serial number. */
fixed char firmwareVersion[20]; /**< Firmware version. */
fixed char targetVoltage[5]; /**< Operate voltage. */
int accessPortNumber; /**< Number of available access port. */
int accessPort; /**< Select access port controller. */
debugConnectMode connectionMode; /**< Select the debug CONNECT mode #debugConnectMode. */
debugResetMode resetMode; /**< Select the debug RESET mode #debugResetMode. */
int isOldFirmware; /**< Check Old ST-LINK firmware version. */
frequencies freq; /**< Supported frequencies #frequencies. */
int frequency; /**< Select specific frequency. */
int isBridge; /**< Indicates if it's Bridge device or not. */
int shared; /**< Select connection type, if it's shared, use ST-LINK Server. */
}
[DllImport(dllPath + "CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public unsafe extern static int getStLinkList(
debugConnectParameters** stLinkList,
int shared
);
I have tried replacing the using "out" and "ref" options. I have tried to find a version of IntPtr that it likes. I keep getting: System.AccessViolationException. I have contacted STMicroelectronics about how to interface with this DLL, but so far they have been no help. The rest of the calls in the DLL are pretty simple but I must have this one working to get started, since it is the information about what JTAG device is actually connected.
Updated (6/12/20) to add the actual calling function and now the calling function copies the structures from unmanaged to managed structures. But the call to getStLinkList() still does not work.
public unsafe static int GetDeviceList(ref debugConnectParameters[] debugParams, int maxCount)
{
IntPtr unManagedListPtr = Marshal.AllocHGlobal(sizeof(debugConnectParameters*));
IntPtr *unManagedListPtrPtr = &unManagedListPtr;
int count = getStLinkList((debugConnectParameters**)unManagedListPtrPtr, 0);
IntPtr copyPtr = unManagedListPtr;
if (count > maxCount) count = maxCount;
for (int i = 0; i < count; i++)
{
debugParams[i] = (debugConnectParameters)Marshal.PtrToStructure(copyPtr, typeof(debugConnectParameters));
copyPtr += sizeof(debugConnectParameters);
}
Marshal.FreeHGlobal(unManagedListPtr);
return (count);
}
I also tried these changes with no luck. I am still seeing access violations. Attempt to read or write protected memory.
[DllImport(dllPath + "CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
unsafe private static extern int getStLinkList(
ref IntPtr stLinkList,
int shared
);
unsafe public static int GetDeviceList(List<debugConnectParameters> list)
{
IntPtr unManagedListPtr = Marshal.AllocHGlobal(sizeof(debugConnectParameters*));
IntPtr copyPtr = unManagedListPtr;
int count = getStLinkList(ref unManagedListPtr, 0);
if (count > 10) count = 10;
for (int i = 0; i < count; i++)
{
debugConnectParameters parameter = (debugConnectParameters)Marshal.PtrToStructure(copyPtr, typeof(debugConnectParameters));
list.Add(parameter);
copyPtr += sizeof(debugConnectParameters);
}
Marshal.FreeHGlobal(unManagedListPtr);
return (count);
}
There are a two things working against us in regards to calling the getStLinkList function of the "CubeProgrammer_API.dll" library in particular. Without correcting them the library will not work, even from C++.
First, the dependencies for that library found in the api/lib folder of the STM32CubeProgrammer installation are not all compiled for x64. The versions in the bin folder are and those are the libraries you'll want to have available at runtime. I've done this by setting the working directory to that directory in the project Debug settings. This was verified by checking the target machine of each DLL in the api/lib and bin folders.
Second, the "CubeProgrammer_API.dll" needs to be initialized before calling getStLinkList by calling the setLoadersPath and setDisplayCallbacks functions. The first function must be provided the path to the "Flash Loader", which is bin/FlashLoader in my case. The setDisplayCallbacks function takes a structure of three function pointers.
The example below might not be the best example of type marshaling, but it will illustrate the process.
public static class CubeProgrammerApi
{
public enum DebugConnectionMode
{
NormalMode = 0,
HotplugMode = 1,
UnderResetMode = 2,
PreResetMode = 3
}
public enum DebugResetMode
{
SoftwareReset = 0,
HardwareReset = 1,
CoreReset = 2
}
[StructLayout(LayoutKind.Sequential)]
public class Frequencies
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
uint[] JtagFrequency;
uint JTagFrequencyNumber;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
uint[] SwdFrequency;
uint SwdFrequencyNumber;
}
[StructLayout(LayoutKind.Sequential)]
public class DebugConnectParameters
{
DebugPort DebugPort;
public int Index;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)]
public string SerialNumber;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string FirmwareVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
public string TargetVoltage;
public int AccesPortNumber;
public int AccessPort;
public DebugConnectionMode connectionMode;
public DebugResetMode resetMode;
public bool IsOldFirmware;
public Frequencies Freqencies;
public int Frequency;
public bool IsBridge;
public int Shared;
}
public delegate void LogMessageReceived(int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message);
public delegate void InitProgressBar();
public delegate void ProgressBarUpdateReceived(int currentProgress, int total);
internal static class NativeMethods
{
public struct DisplayCallbacks
{
public InitProgressBar initProgressBar;
public LogMessageReceived logMessage;
public ProgressBarUpdateReceived loadBar;
}
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "setLoadersPath")]
internal static extern void SetLoadersPath(string path);
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "setDisplayCallbacks")]
internal static extern void SetDisplayCallbacks(ref DisplayCallbacks callbacks);
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "getStLinkList")]
internal static extern int GetStLinkList(IntPtr stLinkList, uint shared);
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void deleteInterfaceList();
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "reset")]
internal static extern int Reset([MarshalAs(UnmanagedType.U4)] DebugResetMode rstMode);
}
public static void SetLoadersPath(string path)
{
NativeMethods.SetLoadersPath(path);
}
public static void SetDisplayCallbacks(InitProgressBar initProgressBar, LogMessageReceived messageReceived, ProgressBarUpdateReceived progressBarUpdate)
{
NativeMethods.DisplayCallbacks callbacksHandle;
callbacksHandle.initProgressBar = initProgressBar;
callbacksHandle.logMessage = messageReceived;
callbacksHandle.loadBar = progressBarUpdate;
NativeMethods.SetDisplayCallbacks(ref callbacksHandle);
}
public static IList<DebugConnectParameters> GetStLinkProgrammers(bool shared = false)
{
var listPtr = Marshal.AllocHGlobal(Marshal.SizeOf<IntPtr>());
var parametersList = new List<DebugConnectParameters>();
try
{
var size = Marshal.SizeOf<DebugConnectParameters>();
var numberOfItems = NativeMethods.GetStLinkList(listPtr, shared ? 1U : 0U);
var listDereference = Marshal.PtrToStructure<IntPtr>(listPtr);
for (var i = 0; i < numberOfItems; i++)
{
var currentItem = Marshal.PtrToStructure<DebugConnectParameters>(listDereference + (i * size));
parametersList.Add(currentItem);
}
NativeMethods.deleteInterfaceList();
}
finally
{
Marshal.FreeHGlobal(listPtr);
}
return parametersList;
}
public static void FreeStLinkProgrammers()
{
NativeMethods.deleteInterfaceList();
}
}
And main:
class Program
{
static void Main(string[] args)
{
CubeProgrammerApi.SetLoadersPath(#"C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\FlashLoader");
CubeProgrammerApi.SetDisplayCallbacks(InitProgressBar, ReceiveMessage, ProgressBarUpdate);
var stLinkList = CubeProgrammerApi.GetStLinkProgrammers();
foreach (var stlink in stLinkList)
{
Console.WriteLine("{0} {1}", stlink.Index, stlink.SerialNumber);
}
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
CubeProgrammerApi.FreeStLinkProgrammers();
}
static void ReceiveMessage(int messgaeType, string message)
{
}
static void InitProgressBar()
{
}
static void ProgressBarUpdate(int currentProgress, int total)
{
}
}
This will output the following with two debuggers attached, a STLink V3 and an STLink V2 respectively. This should get you far enough along to discover the correct types to use.
0 002F001D3038511834333935
1 34FF6D065250343847110943
Press ENTER to exit
I'm trying to set an already installed windows service to automatic delayed start in C#. How do I set a windows service to
Automatic (Delayed Start)
Can't find that value in the ServiceStartMode enum.
Edit:1
public class ServiceAutoStartInfo
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SERVICE_DELAYED_AUTO_START_INFO
{
public bool fDelayedAutostart;
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ChangeServiceConfig2(IntPtr hService, int dwInfoLevel, IntPtr lpInfo);
// Service configuration parameter
const int SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3;
public bool ChangeDelayedAutoStart(IntPtr hService, bool delayed)
{
// Validate service handle
if (hService != IntPtr.Zero)
{
// Create
SERVICE_DELAYED_AUTO_START_INFO info = new SERVICE_DELAYED_AUTO_START_INFO();
// Set the DelayedAutostart property
info.fDelayedAutostart = delayed;
// Allocate necessary memory
IntPtr hInfo = Marshal.AllocHGlobal(Marshal.SizeOf(
typeof(SERVICE_DELAYED_AUTO_START_INFO)));
// Convert structure to pointer
Marshal.StructureToPtr(info, hInfo, true);
// Change the configuration
bool result = ChangeServiceConfig2(hService, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, hInfo);
// Release memory
Marshal.FreeHGlobal(hInfo);
return result;
}
return false;
}
}
This is how I call it:
var controller = new ServiceController(s.ServiceName);
var autoDelay = new ServiceAutoStartInfo();
autoDelay.ChangeDelayedAutoStart(controller.ServiceHandle.DangerousGetHandle(), true);
But with no result.
Look into calling the Windows ChangeServiceConfig2 function, with dwInfoLevel of SERVICE_CONFIG_DELAYED_AUTO_START_INFO and a SERVICE_DELAYED_AUTO_START_INFO struct with fDelayedAutostart set to TRUE.
Or, you can do this with the command line:
sc.exe config <servicename> start= delayed-auto
I'm using the following, which works for me on Windows 7 (when run as admin):
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.ServiceProcess;
namespace ServiceManager
{
/// <summary>
/// Extensions to the ServiceController class.
/// </summary>
public static class ServiceControlerExtensions
{
/// <summary>
/// Set the start mode for the service.
/// </summary>
/// <param name="serviceController">The service controller.</param>
/// <param name="mode">The desired start mode.</param>
public static void SetStartMode(this ServiceController serviceController, ServiceStartModeEx mode)
{
IntPtr serviceManagerHandle = OpenServiceManagerHandle();
IntPtr serviceHandle = OpenServiceHandle(serviceController, serviceManagerHandle);
try
{
if (mode == ServiceStartModeEx.DelayedAutomatic)
{
ChangeServiceStartType(serviceHandle, ServiceStartModeEx.Automatic);
ChangeDelayedAutoStart(serviceHandle, true);
}
else
{
// Delayed auto-start overrides other settings, so it must be set first.
ChangeDelayedAutoStart(serviceHandle, false);
ChangeServiceStartType(serviceHandle, mode);
}
}
finally
{
if (serviceHandle != IntPtr.Zero)
{
CloseServiceHandle(serviceHandle);
}
if (serviceHandle != IntPtr.Zero)
{
CloseServiceHandle(serviceManagerHandle);
}
}
}
private static IntPtr OpenServiceHandle(ServiceController serviceController, IntPtr serviceManagerHandle)
{
var serviceHandle = OpenService(
serviceManagerHandle,
serviceController.ServiceName,
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG);
if (serviceHandle == IntPtr.Zero)
{
throw new ExternalException("Open Service Error");
}
return serviceHandle;
}
private static IntPtr OpenServiceManagerHandle()
{
IntPtr serviceManagerHandle = OpenSCManager(null, null, SC_MANAGER_ALL_ACCESS);
if (serviceManagerHandle == IntPtr.Zero)
{
throw new ExternalException("Open Service Manager Error");
}
return serviceManagerHandle;
}
private static void ChangeServiceStartType(IntPtr serviceHandle, ServiceStartModeEx mode)
{
bool result = ChangeServiceConfig(
serviceHandle,
SERVICE_NO_CHANGE,
(uint)mode,
SERVICE_NO_CHANGE,
null,
null,
IntPtr.Zero,
null,
null,
null,
null);
if (result == false)
{
ThrowLastWin32Error("Could not change service start type");
}
}
private static void ChangeDelayedAutoStart(IntPtr hService, bool delayed)
{
// Create structure that contains DelayedAutoStart property.
SERVICE_DELAYED_AUTO_START_INFO info = new SERVICE_DELAYED_AUTO_START_INFO();
// Set the DelayedAutostart property in that structure.
info.fDelayedAutostart = delayed;
// Allocate necessary memory.
IntPtr hInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SERVICE_DELAYED_AUTO_START_INFO)));
// Convert structure to pointer.
Marshal.StructureToPtr(info, hInfo, true);
// Change the configuration.
bool result = ChangeServiceConfig2(hService, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, hInfo);
// Release memory.
Marshal.FreeHGlobal(hInfo);
if (result == false)
{
ThrowLastWin32Error("Could not set service to delayed automatic");
}
}
private static void ThrowLastWin32Error(string messagePrefix)
{
int nError = Marshal.GetLastWin32Error();
var win32Exception = new Win32Exception(nError);
string message = string.Format("{0}: {1}", messagePrefix, win32Exception.Message);
throw new ExternalException(message);
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr OpenService(
IntPtr hSCManager,
string lpServiceName,
uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode,
SetLastError = true)]
private static extern IntPtr OpenSCManager(
string machineName,
string databaseName,
uint dwAccess);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern Boolean ChangeServiceConfig(
IntPtr hService,
UInt32 nServiceType,
UInt32 nStartType,
UInt32 nErrorControl,
String lpBinaryPathName,
String lpLoadOrderGroup,
IntPtr lpdwTagId,
[In] char[] lpDependencies,
String lpServiceStartName,
String lpPassword,
String lpDisplayName);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ChangeServiceConfig2(
IntPtr hService,
int dwInfoLevel,
IntPtr lpInfo);
[DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle")]
private static extern int CloseServiceHandle(IntPtr hSCObject);
private const uint SERVICE_NO_CHANGE = 0xFFFFFFFF;
private const uint SERVICE_QUERY_CONFIG = 0x00000001;
private const uint SERVICE_CHANGE_CONFIG = 0x00000002;
private const uint SC_MANAGER_ALL_ACCESS = 0x000F003F;
private const int SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SERVICE_DELAYED_AUTO_START_INFO
{
public bool fDelayedAutostart;
}
}
}
namespace ServiceManager
{
public enum ServiceStartModeEx
{
Automatic = 2,
Manual = 3,
Disabled = 4,
DelayedAutomatic = 99
}
}
You call it like this:
var serviceController = new ServiceController("Windows Update");
try
{
serviceController.SetStartMode(ServiceStartModeEx.DelayedAutomatic);
}
finally
{
serviceController.Close();
}
Update: This only works for setting up new services and is not what the OP asked for:
You can use the DelayedAutoStart property of the ServiceInstaller.
installer.DelayedAutoStart = true;
I believe you need to combine both methods ChangeServiceConfig and ChangeServiceConfig2.
pseudo-code follows:
public static void ChangeServiceStartupType(ServiceStartupType type, ...)
{
if (type == AutomaticDelayed)
{
if (ChangeServiceConfig2(.., DelayedAutoStart, ..))
{
ChangeServiceConfig(.., Automatic, ..);
}
}
else
{
ChangeServiceConfig2(.., !DelayedAutoStart, ..)
ChangeServiceConfig(.., type, ..)
}
}
edit: you also need to remove "delayed-automatic" when requesting non-delayed startup-type. Otherwise it won't be possible to set "automatic" type. ("automatic-delayed" overrides "automatic")
SERVICE_DELAYED_AUTO_START_INFO structure documentation states:
fDelayedAutostart
If this member is TRUE, the service is started after other auto-start
services are started plus a short delay. Otherwise, the service is
started during system boot.
This setting is ignored unless the service is an auto-start service.
https://learn.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_delayed_auto_start_info?redirectedfrom=MSDN#members
So I think if the service is not auto-start it won't change it to auto delayed, you would have to use sc.exe
I am trying to P/Invoke the NotifyServiceStatusChange event in C# to check when a service has stopped. I managed to get it to compile and run without any errors, but now, when I stop the service, it doesn't seem to want to notify that its dead. Any ideas why that could be? You can test it out by copying this code into a blank console application; just be sure to replace "My Service Name" with your service name (there are two instances of this string below).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
public delegate void StatusChanged(IntPtr parameter);
public class SERVICE_NOTIFY : MarshalByRefObject
{
public uint dwVersion;
public StatusChanged pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
public struct SERVICE_STATUS_PROCESS {
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, ref IntPtr pNotifyBuffer);
public static SERVICE_NOTIFY notify;
public static GCHandle notifyHandle;
public static IntPtr unmanagedNotifyStructure;
static void Main(string[] args)
{
IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
IntPtr hService = OpenService(hSCM, "My Service Name", (uint)0xF003F);
if (hService != IntPtr.Zero)
{
StatusChanged changeDelegate = ReceivedStatusChangedEvent;
notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = changeDelegate;
notify.pContext = IntPtr.Zero;
notify.dwNotificationStatus = 0;
SERVICE_STATUS_PROCESS process;
process.dwServiceType = 0;
process.dwCurrentState = 0;
process.dwControlsAccepted = 0;
process.dwWin32ExitCode = 0;
process.dwServiceSpecificExitCode = 0;
process.dwCheckPoint = 0;
process.dwWaitHint = 0;
process.dwProcessId = 0;
process.dwServiceFlags = 0;
notify.ServiceStatus = process;
notify.dwNotificationTriggered = 0;
notify.pszServiceNames = Marshal.StringToHGlobalUni("My Service Name");
notifyHandle = GCHandle.Alloc(notify);
unmanagedNotifyStructure = Marshal.AllocHGlobal((IntPtr)(notifyHandle));
NotifyServiceStatusChange(hService, (uint)0x00000001, ref unmanagedNotifyStructure);
Console.WriteLine("Waiting for the service to stop. Press enter to exit.");
Console.ReadLine();
}
}
}
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
Console.WriteLine("Service stopped.");
}
}
}
If you want to keep the functionality you are attempting at present, you will need to multithread.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class SERVICE_NOTIFY
{
public uint dwVersion;
public IntPtr pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
[DllImportAttribute("kernel32.dll", EntryPoint = "SleepEx")]
public static extern uint SleepEx(uint dwMilliseconds, [MarshalAsAttribute(UnmanagedType.Bool)] bool bAlertable);
public static SERVICE_NOTIFY notify;
public static GCHandle notifyHandle;
public static IntPtr unmanagedNotifyStructure;
static void Main(string[] args)
{
IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
IntPtr hService = OpenService(hSCM, "Apache2.2", (uint)0xF003F);
if (hService != IntPtr.Zero)
{
StatusChanged changeDelegate = ReceivedStatusChangedEvent;
notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
notify.pContext = IntPtr.Zero;
notify.dwNotificationStatus = 0;
SERVICE_STATUS_PROCESS process;
process.dwServiceType = 0;
process.dwCurrentState = 0;
process.dwControlsAccepted = 0;
process.dwWin32ExitCode = 0;
process.dwServiceSpecificExitCode = 0;
process.dwCheckPoint = 0;
process.dwWaitHint = 0;
process.dwProcessId = 0;
process.dwServiceFlags = 0;
notify.ServiceStatus = process;
notify.dwNotificationTriggered = 0;
notify.pszServiceNames = Marshal.StringToHGlobalUni("Apache2.2");
notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
unmanagedNotifyStructure = notifyHandle.AddrOfPinnedObject();
NotifyServiceStatusChange(hService, (uint)0x00000001, unmanagedNotifyStructure);
GC.KeepAlive(changeDelegate);
Console.WriteLine("Waiting for the service to stop. Press enter to exit.");
while (true)
{
try
{
string keyIn = Reader.ReadLine(500);
break;
}
catch (TimeoutException)
{
SleepEx(100,true);
}
}
notifyHandle.Free();
}
}
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void StatusChanged(IntPtr parameter);
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
Console.WriteLine("Service stopped.");
}
}
}
class Reader
{
private static Thread inputThread;
private static AutoResetEvent getInput, gotInput;
private static string input;
static Reader()
{
inputThread = new Thread(reader);
inputThread.IsBackground = true;
inputThread.Start();
getInput = new AutoResetEvent(false);
gotInput = new AutoResetEvent(false);
}
private static void reader()
{
while (true)
{
getInput.WaitOne();
input = Console.ReadLine();
gotInput.Set();
}
}
public static string ReadLine(int timeOutMillisecs)
{
getInput.Set();
bool success = gotInput.WaitOne(timeOutMillisecs);
if (success)
return input;
else
throw new TimeoutException("User did not provide input within the timelimit.");
}
}
Read related question here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/f68fb826-036a-4b9c-81e6-4cbd87931feb/notifyservicestatuschange-not-working-in-c-for-windows-service-notification
Important quote: "the system invokes the specified callback function as an asynchronous procedure call (APC) queued to the calling thread. The calling thread must enter an alertable wait"
I don't remember whether .NET framework 4 uses alertable waiting when you enter Thread.Sleep or some form of Wait on waithandles, even though it uses alertable waiting for asynchronous I/O, for internal timer threads etc.
However just try Thread.Sleep or some flavor of Wait on some waithandle, instead of Console.ReadLine, make sure that your thread is blocked by those APIs at the time when you kill the service. This might do the magic - but, to my knowledge, this is a dangerous way, because .NET runtime does not expect user code to be executed on an APC. At least, try not to use NET framework resources or absolutely any APIs (especially synchronization-related or memory allocation) directly from your callback - just set some primitive variable and quit.
With APCs, the safest solution for you would be to have the callback implemented in some kind of native module, and also scheduled from some non-.NET thread, interoperating with managed code through shared variables, a pipe, or COM interface.
Or, as Hans Passant suggested in another copy of your question, just do polling from managed code. Absolutely safe, easy to implement, guaranteed to work.
Excellent source of relevant information is Joe Duffy's book (he covers a lot of topics, and alertable waits and .NET in particular): http://www.amazon.com/Concurrent-Programming-Windows-Joe-Duffy/dp/032143482X
UPDATE: Just consulted with Joe Duffy's book, yes indeed, scheduling .NET code on an APC may result in deadlocks, access violations and generally unpredictable behavior. So the answer is simple: don't do APC from a managed thread.
Simplified a lot of this from #Motes' answer...(EDIT: I put it into a class that people can use to easily wait for a service to stop; it will block!
Edit Again: Made sure this worked if you force garbage collection with GC.Collect() anywhere in the function...turns out, you DO need the SERVICE_STATUS_PROCESS.
Another Edit: Made sure it works if you abort your thread (note on that: can't abort sleeping threads so if you plan to abort this thread...then make sure you give it a timeout at least so the finalizer can run after the timeout hits), also added timeouts. Also ensured mapping 1-to-1 of the OS thread to the current .NET thread.
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ServiceAssistant
{
class ServiceHelper
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class SERVICE_NOTIFY
{
public uint dwVersion;
public IntPtr pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll")]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll")]
static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll")]
static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
[DllImportAttribute("kernel32.dll")]
static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
[DllImport("advapi32.dll")]
static extern bool CloseServiceHandle(IntPtr hSCObject);
delegate void StatusChangedCallbackDelegate(IntPtr parameter);
/// <summary>
/// Block until a service stops, is killed, or is found to be already dead.
/// </summary>
/// <param name="serviceName">The name of the service you would like to wait for.</param>
/// <param name="timeout">An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.</param>
public static void WaitForServiceToStop(string serviceName, uint timeout = uint.MaxValue)
{
// Ensure that this thread's identity is mapped, 1-to-1, with a native OS thread.
Thread.BeginThreadAffinity();
GCHandle notifyHandle = default(GCHandle);
StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
IntPtr hSCM = IntPtr.Zero;
IntPtr hService = IntPtr.Zero;
try
{
hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
hService = OpenService(hSCM, serviceName, (uint)0xF003F);
if (hService != IntPtr.Zero)
{
SERVICE_NOTIFY notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
notify.ServiceStatus = new SERVICE_STATUS_PROCESS();
notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
IntPtr pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject();
NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure);
SleepEx(timeout, true);
}
}
}
finally
{
// Clean up at the end of our operation, or if this thread is aborted.
if (hService != IntPtr.Zero)
{
CloseServiceHandle(hService);
}
if (hSCM != IntPtr.Zero)
{
CloseServiceHandle(hSCM);
}
GC.KeepAlive(changeDelegate);
if (notifyHandle != default(GCHandle))
{
notifyHandle.Free();
}
Thread.EndThreadAffinity();
}
}
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
}
}
}
Yes! We did it. What a journey it has been.
I have TXT file in shard memory.
The code is at the end. I've been attempting to get it out of the memory and write to a file in the C:\ drive.
But i get a error:
Type 'SharedMemSaveToFile.SharedMemSaver+Data' cannot be marshaled as an
unmanaged structure; no meaningful size or offset can be computed.
If i change the code to write the Memory the CMD, it works, so i know the memory is there. I've also tried using these to write the TXT:
System.IO.StreamWriter file = new System.IO.StreamWriter("c:\\file.txt");
file.WriteLine(d);
and:
using (StreamWriter outfile = new StreamWriter(d + #"C:\\file.txt"))
{
outfile.Write(sb.ToString());
}
and:
StreamWriter sw = new StreamWriter("file.txt");
sw.Write(d);
sw.Close();
Thanks!
public class Data
{
static void Main(string[] args)
{
SharedMemSaver sf = new SharedMemSaver();
sf.OpenView();
String d = sf.GetData();
System.IO.File.WriteAllText(#"C:\file.txt", d);
}
}
#region Win32 API stuff
public const int FILE_MAP_READ = 0x0004;
[DllImport("Kernel32", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern IntPtr OpenFileMapping(int dwDesiredAccess,
bool bInheritHandle, StringBuilder lpName);
[DllImport("Kernel32", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern IntPtr MapViewOfFile(IntPtr hFileMapping,
int dwDesiredAccess, int dwFileOffsetHigh, int dwFileOffsetLow,
int dwNumberOfBytesToMap);
[DllImport("Kernel32.dll")]
internal static extern bool UnmapViewOfFile(IntPtr map);
[DllImport("kernel32.dll")]
internal static extern bool CloseHandle(IntPtr hObject);
#endregion
private bool fileOpen = false;
private IntPtr map;
private IntPtr handle;
~SharedMemSaver()
{
CloseView();
}
public bool OpenView()
{
if (!fileOpen)
{
StringBuilder sharedMemFile = new StringBuilder("Mem_Values");
handle = OpenFileMapping(FILE_MAP_READ, false, sharedMemFile);
if (handle == IntPtr.Zero)
{
throw new Exception("Unable to open file mapping.");
}
map = MapViewOfFile(handle, FILE_MAP_READ, 0, 0, Marshal.SizeOf((Type)typeof(Data)));
if (map == IntPtr.Zero)
{
throw new Exception("Unable to read shared memory.");
}
fileOpen = true;
}
return fileOpen;
}
public void CloseView()
{
if (fileOpen)
{
UnmapViewOfFile(map);
CloseHandle(handle);
}
}
public String GetData()
{
if (fileOpen)
{
String data = (String)Marshal.PtrToStringAuto(map);
return data;
}
else
{
return null;
}
}
}
}
I would strongly recommend to use the built-in MemoryMappedFile class (new in .NET 4).
See Yahia's answer for the solution.
But trying to fix your code, the error message says all:
What are you trying to get with Marshal.SizeOf((Type)typeof(Data))?
It has no size, because it holds not data.
Looking at the MSDN doc. of MapViewOfFile's last parameter, "If this parameter is 0 (zero), the mapping extends from the specified offset to the end of the file mapping."