C# Get System Audio Volume via Windows API [duplicate] - c#

This question already has answers here:
C# get master volume level/precent
(3 answers)
Closed 8 years ago.
I've been trying to get the current system volume using C# / Windows API. I'm on Windows 8.1 though I would like the solution to also work on Windows 7.
This:
[DllImport("winmm.dll")]
public static extern int waveOutGetVolume(IntPtr hwo, out uint dwVolume);
does not work, with the system volume after Windows XP.
I have tried this:
[DllImport("Audioses.dll", EntryPoint = "GetMasterVolume", SetLastError = true)]
static extern int GetMasterVolume(out float pfLevelDB);
public void getVolume()
{
float f = 0.0F;
int i = GetMasterVolume(out f);
MessageBox.Show(f.ToString());
}
However the application never gets to the MessageBox.Show(...), though running line-by-line shows that it gets to GetMasterVolume(out f) then fails. I think something must be wrong with my declaration or usage.
Output shows: System.EntryPointNotFoundException
GetMasterVolumeLevel: http://msdn.microsoft.com/en-us/library/windows/desktop/dd316533(v=vs.85).aspx

EntryPointNotFound indicates that the declaration is indeed mistaken, and your implementation cannot find the method you're requesting within the specified dll. The biggest I see here is that the ISimpleAudioVolume is an interface, and thus the desired method is an instance, not a static, method, and requires more work to grab an instance of the appropriate type and return the desired volume levels. Note that MSDN and on the interface itself mentions needing to start an audio session to operate through this interface.

Related

How do I get the name of the current executable in C#? (.NET 5 edition)

How do I get the name the executable was invoked as (equivalent to C's argv[0])? I actually need to handle somebody renaming the executable and stuff like that.
There's a famous question with lots of answers that don't work. Answers tried:
System.AppDomain.CurrentDomain.FriendlyName
returns the name it was compiled as
System.Diagnostics.Process.GetCurrentProcess().ProcessName
strips extension (ever rename a .exe to a .com?), also sees through symbolic links
Environment.GetCommandLineArgs()[0]
It returns a name ending in .dll, clearly an error.
Assembly.GetEntryAssembly().Location
Returns null
System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName
Returns a .dll name again.
The documentation for .net 5.0 says Environment.GetCommandLineArguments()[0] works; however it doesn't actually work. It somehow sees through symbolic links and returns the real executable name.
What I'm trying to do is link all of our stuff into a single multi-call binary so I can use the .net 5 framework reducer on the resulting binary so I don't have to ship about 30MB of .net 5 framework we're not using. I really don't want to do a platform ladder and P/Invoke a bunch of stuff unless I have to.
I'm after argv[0] directly, not the running process executable name. In the case of symbolic links, these differ.
Came across this with .NET 6, where Process.GetCurrentProcess().MainModule?.FileName seems to be working fine now, and there's also Environment.ProcessPath.
If targeting Windows only, it might be safer (more predictable) to use interop. Below are some options, including the native GetModuleFileName and GetCommandLine:
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("Process.GetCurrentProcess().MainModule?.FileName");
Console.WriteLine(Process.GetCurrentProcess().MainModule?.FileName);
Console.WriteLine();
Console.WriteLine("Assembly.GetExecutingAssembly().Location");
Console.WriteLine(Assembly.GetExecutingAssembly().Location);
Console.WriteLine();
Console.WriteLine("Environment.ProcessPath");
Console.WriteLine(Environment.ProcessPath);
Console.WriteLine();
Console.WriteLine("Environment.CommandLine");
Console.WriteLine(Environment.CommandLine);
Console.WriteLine();
Console.WriteLine("Environment.GetCommandLineArgs()[0]");
Console.WriteLine(Environment.GetCommandLineArgs()[0]);
Console.WriteLine();
Console.WriteLine("Win32.GetProcessPath()");
Console.WriteLine(Win32.GetProcessPath());
Console.WriteLine();
Console.WriteLine("Win32.GetProcessCommandLine()");
Console.WriteLine(Win32.GetProcessCommandLine());
Console.WriteLine();
public static class Win32
{
private const int MAX_PATH = 260;
private const int INSUFFICIENT_BUFFER = 0x007A;
private const int MAX_UNICODESTRING_LEN = short.MaxValue;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr GetCommandLine();
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
private static extern int GetModuleFileName(
IntPtr hModule, StringBuilder lpFilename, [MarshalAs(UnmanagedType.U4)] int nSize);
public static string GetProcessCommandLine()
{
return Marshal.PtrToStringUni(GetCommandLine()) ??
throw new Win32Exception(nameof(GetCommandLine));
}
public static string GetProcessPath()
{
var buffer = new StringBuilder(MAX_PATH);
while (true)
{
int size = GetModuleFileName(IntPtr.Zero, buffer, buffer.Capacity);
if (size == 0)
{
throw new Win32Exception();
}
if (size == buffer.Capacity)
{
// double the buffer size and try again.
buffer.EnsureCapacity(buffer.Capacity * 2);
continue;
}
return Path.GetFullPath(buffer.ToString());
}
}
}
The output when running via dotnet run:
Process.GetCurrentProcess().MainModule?.FileName
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Assembly.GetExecutingAssembly().Location
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Environment.ProcessPath
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Environment.CommandLine
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Environment.GetCommandLineArgs()[0]
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Win32.GetProcessPath()
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Win32.GetProcessCommandLine()
"C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe"
Oh, and for a Windows Forms application, there always has been Application.ExecutablePath.
Updated, running it on Ubuntu 22.04 with .NET 6.0.2 (with Win32 interop removed), either via dotnet run or directly as ./ProcessPath:
Process.GetCurrentProcess().MainModule?.FileName
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath
Assembly.GetExecutingAssembly().Location
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
Environment.ProcessPath
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath
Environment.CommandLine
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
Environment.GetCommandLineArgs()[0]
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
After watching everything fail, it became necessary to P/Invoke stuff to make this work. While Process.GetCurrentProcess().MainModule?.FileName reliably returns the executable binary (at least when not running under the debugger), this does not provide the command invocation the binary was launched with.
On Windows, GetCommandLine() is P/Invokable and needs only some parsing to get the information. On *n?x, reading /proc/self/cmdline does the same job.
I built a library encapsulating this. https://github.com/joshudson/Emet/tree/master/MultiCall You can find binaries on nuget.org ready to go.
I should have self-answered a long time ago. Better late than never. Nobody seemed to care until now.
Using .NET Core 3.1, this worked for me to restart a currently running program.
string filename = Process.GetCurrentProcess().MainModule.FileName;
System.Diagnostics.Process.Start(filename);
// Closes the current process
Environment.Exit(0);

How do I detect if a Windows device is touch-enabled

How do I detect if a device is touch-enabled in C# for a WinForms app (Not WPF).
I found information on GetSystemMetrics. But I can't find how to use this in C#.
I tried using System.Windows.Input.Tablet class. But it's not coming up in C#, even though I am using .NET Framework 4.5.
I tried using System.Windows.Devices. But it's not coming up in C#, even though I am using .NET Framework 4.5.
I have also checked Detect whether a Windows 8 Store App has a touch screen and How to detect a touch enabled device (Win 8, C#.NET), which would seem to make this question a duplicate. However, neither of these answers my question.
GetSystemMetrics seems to be the right way to go. It should be accessed through P/Invoke like this:
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);
public static bool IsTouchEnabled()
{
const int MAXTOUCHES_INDEX = 95;
int maxTouches = GetSystemMetrics(MAXTOUCHES_INDEX);
return maxTouches > 0;
}
As taken from this answer
var hasTouch = Windows.Devices.Input
.PointerDevice.GetPointerDevices()
.Any(p => p.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch);

libvlc media player in C#

Hey guys and girls :) ok so i ran this project ->
http://www.helyar.net/2009/libvlc-media-player-in-c-part-2/ and it worked perfectly (he was using .net 2.0) however when i try anything above 3.5 it gives ->
Unable to load DLL ‘libvlc’: The specified module could not be found. (Exception from HRESULT: 0x8007007E)
is there any workaround someone has done that sorts this out? MANY thanks ppl :D:D:D:D
There are two things that must be done when using that example with the new 2.0.x VLC releases. First, you have to somehow add the libvlc DLL to the search path. I used a call to SetDllDirectory to do the trick. You declare it as:
static class LibVlc
{
. . .
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetDllDirectory(string lpPathName);
. . .
}
Then you can call this method with the root folder of the VLC installation. On my PC, I called it as follows:
LibVlc.SetDllDirectory(#"C:\Program Files (x86)\VideoLAN\VLC");
Obviously, for a program being distributed this parameter should be configurable.
Next, the VLC API's have apparently changed because none of the methods require an exception object to be passed in anymore. It looks like return values from the methods should be checked (for example, libvlc_new() returns NULL if there was an error). I haven't tried passing in the exception object by reference like he does but the calls all work fine without it (and my interfaces now match the VLC API exactly). I also specify the calling convention to use when doing interop, just to be clear to the runtime what I expect for parameter passing order and such. For example, here are my defines for libvlc_new and libvlc_release:
[DllImport("libvlc", CallingConvention=CallingConvention.Cdecl)]
public static extern IntPtr libvlc_new(int argc,
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.LPStr)] string[] argv);
[DllImport("libvlc", CallingConvention=CallingConvention.Cdecl)]
public static extern void libvlc_release(IntPtr instance);
I hope this helps!
You must copy libvlc.dll to your bin/debug folder. It must be the one from your VLC installation folder (C:\program files\videolan\vlc)

Interface to change volume and play sound for HDA Audio device under Windows CE 6.0?

I develop C# .Net CF applications for a Win CE device, and am having problems getting the speaker volume change to affect actual volume when playing sound.
The interface I use is:
int waveOutSetVolume(IntPtr hMod, UInt32 dwVolume);
int PlaySound(string szSound, IntPtr hMod, int flags);
The code I use worked well with our old device which had the following setup:
AC'97 Audio codec, Windows CE 5, .Net CF 2.0.
However, on the new device the sound is played but I'm not able to change the volume. The setup is as follows:
HDA Audio codec, Windows CE 6, .Net CF 3.5.
I am uncertain whether this problem is within the Windows CE 6 OS image (e.g. missing/incorrect audio driver), or if I use the incorrect interface in my C# code.
Any help and ideas are most welcome!
Thanks,
Karl
Additional details:
Code:
public unsafe int SetVolume(int newVolumeInPercent)
{
UInt32 newVol = (UInt32)((double)(newVolumeInPercent * ushort.MaxValue) / 100.0);
newVol = newVol + (newVol << 16);
int resultSetVolume = waveOutSetVolume(IntPtr.Zero, newVol);
return (int)Math.Round((double)resultSetVolume * 100 / ushort.MaxValue);
}
public void playSound(string soundFile)
{
PlaySound(soundFile, IntPtr.Zero, (int)(Flags.SND_ASYNC | Flags.SND_FILENAME));
}
[DllImport("CoreDll.dll")]
private extern static int waveOutSetVolume(IntPtr hMod, UInt32 dwVolume);
[DllImport("CoreDll.dll", EntryPoint = "PlaySound", SetLastError = true)]
private extern static int PlaySound(string szSound, IntPtr hMod, int flags);
private enum Flags
{
SND_ASYNC = 0x0001,
SND_FILENAME = 0x00020000,
}
As you see in the code, I use the argument percentage volume for both the left and right channel.
Using Windows CE Remote Process Viewer, I can see that the audio driver (i.e. "jwavehda.dll") is loaded. Also the "waveapi.dll" (generic Window wave api?) is loaded.
I do get sound when tapping the screen, and using the "PlaySound" function I am able to play a wave file. The only problem is that I cannot affect the volume.
Thanks!
I'd need to see your calling code (and your p/invoke declaration here are incomplete as well) to be sure. Are you aware that the waveOutSetVolume dwVolume is split into two words, the upper work being the left channel volume and the lower work being the right channel volume? The value you're sending in might be affecting the behavior (you didn't show that part of your code).
The fact that the code worked on one platform but fails on another indicates to me that it's likely an OS/Platform issue. Do you get audio for things like screen taps or other system events? Do you have an audio control panel applet? Did you look for an audio driver in the registry to make sure it both exists in the OS and is also loaded?

ShSetFolderPath works on win7, doesn't on XP

I'm trying to use ShSetFolderPath function in C#. I work on Win7, I've managed to use ShSetKnownFolderPath and it works fine.
Since this function is unavaible in WinXP, i tried to invoke ShSetFolderPath. Because i'm not familiar with invoking, I've done some searching and found something on some French forum. I don't speak French, but this declaration makes sense (as written in Remarks of function documentation in MSDN library):
[DllImport( "Shell32.dll", CharSet = CharSet.Unicode, EntryPoint = "#232" ) ]
private static extern int SHSetFolderPath( int csidl, IntPtr hToken, uint flags, string path );
I call it like that:
private static int CSIDL_DESKTOP = 0x0000;
public static void SetDesktopPath(string path)
{
int ret;
ret = SHSetFolderPath(CSIDL_DESKTOP, IntPtr.Zero, 0, path);
if (ret != 0)
{
Console.WriteLine(ret);
Console.WriteLine(Marshal.GetExceptionForHR(ret));
}
}
It works in Win7, but in XP function returns -2147024809, which means "Value does not fall within the expected range".
My guess is, it's something wrong with Dll importing. Any idea?
Funny thing.
I've taken another look at CSIDL list. And I've realized I was trying to change some "low-level" reference (i guess) to desktop:
CSIDL_DESKTOP = 0x0000, // <desktop>
While I actually wanted to change just folder location, and i should've use this:
CSIDL_DESKTOPDIRECTORY = 0x0010, // <user name>\Desktop.
And THIS works.
It explains everything. Shame on me.
Nah, that's not it. The error code, converted to hex, is 0x80070057. The 7 indicates a Windows error, 57 is error code 87, ERROR_INVALID_PARAMETER, "The parameter is incorrect".
A couple of possible reasons. First is that entry point #232 isn't actually the entry point for SHSetFolderPath(). You might be calling a different function, it wouldn't know what to do with the argument values you pass. Hard to say, it is an unnamed entry point on XP's version of shell32.dll. Or it could be that XP just isn't happy about you changing the desktop folder path. Not that surprising, there's a heckofalot it has to do to actually implement that, refreshing all Explorer.exe views, rebuilding the desktop contents and whatnot.
Check this thread for possible help.

Categories