How to shorten a path in c# and keep it valid - c#

I work in a place where directories have such a looong name and are in such a looong tree.
And I'm having problems with too long path names for folders in an external applicatoin (I can't change this external application, but I can give it shortened path names).
I know Microsoft operating systems can shorten path names such as transforming C:\TooLongName\TooLongSubDirectory in something like C:\TooLon~1\TooLon~1.
But how can I do this in C# and still keep the nave valid and usable?
PS: I'm not using the standard FileInfo and DirectoryInfo classes, I'm using just strings that will be sent to an external application that I cannot change in any way.

If you are unable to use the long path support build into Windows 10 you are able to use the Win32 command GetShortPathName . In order to generate a suitable path.
class Program
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern uint GetShortPathName(
[MarshalAs(UnmanagedType.LPTStr)]
string lpszLongPath,
[MarshalAs(UnmanagedType.LPTStr)]
StringBuilder lpszShortPath,
uint cchBuffer);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern uint GetShortPathName(string lpszLongPath, char[] lpszShortPath, int cchBuffer);
static void Main(string[] args)
{
StringBuilder builder = new StringBuilder(260);
var shortPath = GetShortPathName(#"C:\Projects\Databases\ReallllllllllllllyLOOOOOOOOOOOOOOOOOOOOOONGPATHHHHHHHHHHH\StillllllllllllllllllGOoooooooooooooooooooooooing", builder, (uint)builder.Capacity);
Console.WriteLine(builder.ToString());
Console.ReadKey();
}
}
Produces C:\Projects\DATABA~1\REALLL~1\STILLL~1

Related

CA2101: Specify marshaling for P-Invoke string arguments

I am trying to expose a C function which takes a UTF-8 string to C# (dotnet 5.0). I am getting a warning which does not make sense to me. Here is a simple way to reproduce it, using fopen(3):
[DllImport("libc.so.6", CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr fopen([MarshalAs(UnmanagedType.LPUTF8Str)] string pathname, string mode);
Visual Studio 2019 is reporting a warning:
From the documentation, it seems I need to set CharSet.Ansi in my case:
https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.charset?view=net-5.0
and use UnmanagedType.LPUTF8Str:
https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-strings#strings-used-in-platform-invoke
What did I misunderstood from the documentation ?
Technically this is somewhat a duplicate of:
https://stackoverflow.com/a/13369930/136285
which suggests to add BestFitMapping = false, ThrowOnUnmappableChar = true
In my case the suggested code 'Show potential fixes' ([MarshalAs(UnmanagedType.LPWStr)]) was just bogus (but that is a different issue).
So correct solution is:
[DllImport("libc.so.6", CharSet = CharSet.Ansi, ExactSpelling = true, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr fopen([MarshalAs(UnmanagedType.LPUTF8Str)] string pathname, string mode);

Get full path and convert to constant

Good day. I'm trying to remake dll for my needs. Namely, change the search path to dll.
DllImport:
[DllImport(NativeLibraryName, EntryPoint = "SteamAPI_Init", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool SteamAPI_Init();
Constant:
internal const string NativeLibraryName = #"steam_api64";
Get path to folder:
public static string SteamFolderPath()
{
var r = Microsoft.Win32.Registry.GetValue(#"HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Valve\Steam", "InstallPath", null);
var rpath = r + "/steamapps/common/mygame/steam_api64";
return r.ToString();
}
When i try to do something like this, errors take off:
internal const string NativeLibraryName = SteamFolderPath() + #"steam_api64";
I tried static readonly string. Nothing helps. Hope only for you) How to be? How to try to get the first path first, and then bring it to a constant?
p.s. I'm redoing the Steamworks.NET library.
A constant has to be known at compile time. It can't depend on the return value of a function.
You'll need to use SetDllDirectory to add the Steam path to the DLL search path:
[DllImport("kernel32")]
static extern bool SetDllDirectory(string lpPathName);
string SteamPath = Microsoft.Win32.Registry.GetValue(#"HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Valve\Steam", "InstallPath", null) + #"\steamapps\common\mygame\steam_api64";
SetDllDirectory(SteamPath);
[DllImport("steam_api64")]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool SteamAPI_Init();

Error when trying to replace an UNC path by a string

Sorry for some maybe very basic questions. I simply wanted to replace the UNC path by a string. These lines with the UNC path for a C++ connection perfectly works:
[DllImport(C:\\Users\\SJ\\Documents\\VS2015\\Projects\\P_01\\Debug\\EV_01.dll",
EntryPoint = "DDentry", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern void DDentry
(
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
string[,] pArrayStr
);
Replacing the UNC path by a string gives an error "An object reference is required for the non-static field, method, or property"
string UNCpath = #"C:\\Users\\SJ\\Documents\\VS2015\\Projects\\P_01\\Debug\\EV_01.dll";
[DllImport(UNCpath,
EntryPoint = "DDentry", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern void DDentry
(
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
string[,] pArrayStr
);
Many thank for your ideas ..
You cannot pass the instance value UNCPath into an attribute like that. It would need to be a constant. Also, if you use the double-backslash escape sequences, you can't use the # prefix to the string.
Try this:
const string UNCpath = "C:\\Users\\SJ\\Documents\\VS2015\\Projects\\P_01\\Debug\\EV_01.dll";
[DllImport(UNCpath,
EntryPoint = "DDentry", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern void DDentry
(
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
string[,] pArrayStr
);
You are trying to use a non-constant string with an attribute and it isn't allowed. You must declare your string as "const".

Dll not found vcruntime140.dll when deploying as file for x86 and x64

Currently i get the error message, that vcruntime140.dll was not found on a client computer. But I'm delivering the following dlls for x64 and x86:
vcruntime140.dll
vccorlib140.dll
msvcp140.dll
concrt140.dll
in a \bin32\ and \bin64\ directory (for each platform). This directories are registered on application startup:
Dll-Import
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool AddDllDirectory(string lpPathName);
And for x86 the registration code:
if (System.Environment.OSVersion.Version.Major > 5)
{
AddDllDirectory(System.Windows.Forms.Application.StartupPath + "\\bin32");
}
else
{
SetDllDirectory(System.Windows.Forms.Application.StartupPath + "\\bin32");
}
What do I need to do additionally?
Thank you all!

Set DllImport attribute dynamically

I am making use of an external unmanaged dll using PInvoke and the DllImport attribute. eg.
[DllImport("mcs_apiD.dll", CharSet = CharSet.Auto)]
private static extern byte start_api(byte pid, byte stat, byte dbg, byte ka);
I am wondering if it is possible to alter the dll file details (mcs_apiD.dll in this example) dynmically in some manner, if for instance I wanted to build against another dll version
Yes this is possible, you'll have to do part of the job that the P/Invoke marshaller does. Loading the DLL and finding the entry point of the exported function. Start by declaring a delegate whose signature matches the exported function:
private delegate byte start_api(byte pid, byte stat, byte dbg, byte ka);
Then use code like this:
using System.ComponentModel;
using System.Runtime.InteropServices;
...
static IntPtr dllHandle;
...
if (dllHandle == IntPtr.Zero) {
dllHandle = LoadLibrary("mcs_apiD.dll");
if (dllHandle == IntPtr.Zero) throw new Win32Exception();
}
IntPtr addr = GetProcAddress(dllHandle, "_start_api#16");
if (addr == IntPtr.Zero) throw new Win32Exception();
var func = (start_api)Marshal.GetDelegateForFunctionPointer(addr, typeof(start_api));
var retval = func(1, 2, 3, 4);
...
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string name);
Lots of ways to get this wrong of course. Do note that you have to use the actual exported name from the DLL, you no longer get the help from the P/Invoke marshaller to help with name decoration. Use dumpbin.exe /exports on the DLL if you are not sure what the export name looks like.
you can't change the name of the dll but you can alter the path of the library being loaded (like by reading it from the registry or a configuration file) and load it manually with LoadLibrary kernel32's function: see my answer there.

Categories