CA2101: Specify marshaling for P-Invoke string arguments - c#

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

Related

Xamarin Android: What is the correct way to Marshal a string from C# to a Unicode string in C++?

I am writing a Xamarin Android app and trying to also write a C++ native library that the Xamarin app needs to pass data to. I need to pass a Unicode string to the library, but I am getting some strange behavior when I try.
In my native shared library, I have the following code:
extern "C" void logANSI(const char* data) {
__android_log_print(ANDROID_LOG_INFO, "StringMarshaling", "ANSI data: %s", data);
}
extern "C" void logUnicode(const wchar_t* data) {
__android_log_print(ANDROID_LOG_INFO, "StringMarshaling", "Unicode data: %ls", data);
}
On the C# side, I have the following DllImport declarations:
[DllImport("StringMarshaling", EntryPoint = "logANSI", SetLastError = true, CharSet = CharSet.Ansi, ThrowOnUnmappableChar = true)]
public static extern void LogANSI([MarshalAs(UnmanagedType.LPStr)] string data);
[DllImport("StringMarshaling", EntryPoint = "logUnicode", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern void LogUnicode([MarshalAs(UnmanagedType.LPWStr)] string data);
Then I have the following code in my Activity:
StringMarshaling.LogANSI("12345");
StringMarshaling.LogUnicode("12345");
In the log output, I see:
05-17 18:07:12.437 Unitech PA700 Info 8351 StringMarshaling ANSI data: 12345
05-17 18:07:12.437 Unitech PA700 Info 8351 StringMarshaling Unicode data: 1
How can I get the full string, and not just the first character?
__android_log_print accepts a char *, so:
Using this C/C++ function:
extern "C" void logUnicode(const wchar_t* data) {
std::wstring fooStr = std::wstring(data);
__android_log_print(ANDROID_LOG_INFO, "StringMarshaling", "Unicode data: %s", fooStr.c_str());
}
With this DllImport:
[DllImport("StringMarshaling", EntryPoint = "logUnicode", SetLastError = true)]
public static extern void LogUnicode(string data);
Calling:
StringMarshaling.LogUnicode("1🍣2🍣3🍣4🍣5");
Will result in:
[StringMarshaling] Unicode data: 1🍣2🍣3🍣4🍣5

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".

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

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

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