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".
Related
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);
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
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();
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
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.