I am having an interesting problem with using pinvoke in C# to call _snwprintf. It works for integer types, but not for floating point numbers.
This is on 64-bit Windows, it works fine on 32-bit.
My code is below, please keep in mind that this is a contrived example to show the behavior I am seeing.
class Program
{
[DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern int _snwprintf([MarshalAs(UnmanagedType.LPWStr)] StringBuilder str, IntPtr length, String format, int p);
[DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern int _snwprintf([MarshalAs(UnmanagedType.LPWStr)] StringBuilder str, IntPtr length, String format, double p);
static void Main(string[] args)
{
Double d = 1.0f;
Int32 i = 1;
Object o = (object)d;
StringBuilder str = new StringBuilder(32);
_snwprintf(str, (IntPtr)str.Capacity, "%10.1lf", (Double)o);
Console.WriteLine(str.ToString());
o = (object)i;
_snwprintf(str, (IntPtr)str.Capacity, "%10d", (Int32)o);
Console.WriteLine(str.ToString());
Console.ReadKey();
}
}
The output of this program is
0.0
1
It should print 1.0 on the first line and not 0.0, and so far I am stumped.
I'm not exactly sure why your calls do not work, but the secured versions of these methods do work properly in both x86 and x64.
The following code does work, as expected:
class Program
{
[DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern int _snwprintf_s([MarshalAs(UnmanagedType.LPWStr)] StringBuilder str, IntPtr bufferSize, IntPtr length, String format, int p);
[DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern int _snwprintf_s([MarshalAs(UnmanagedType.LPWStr)] StringBuilder str, IntPtr bufferSize, IntPtr length, String format, double p);
static void Main(string[] args)
{
// Preallocate this to a given length
StringBuilder str = new StringBuilder(100);
double d = 1.4;
int i = 7;
float s = 1.1f;
// No need for box/unbox
_snwprintf_s(str, (IntPtr)100, (IntPtr)32, "%10.1lf", d);
Console.WriteLine(str.ToString());
_snwprintf_s(str, (IntPtr)100, (IntPtr)32, "%10.1f", s);
Console.WriteLine(str.ToString());
_snwprintf_s(str, (IntPtr)100, (IntPtr)32, "%10d", i);
Console.WriteLine(str.ToString());
Console.ReadKey();
}
}
It is possible with the undocumented __arglist keyword:
using System;
using System.Text;
using System.Runtime.InteropServices;
class Program {
[DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern int _snwprintf(StringBuilder str, int length, String format, __arglist);
static void Main(string[] args) {
Double d = 1.0f;
Int32 i = 1;
String s = "nobugz";
StringBuilder str = new StringBuilder(666);
_snwprintf(str, str.Capacity, "%10.1lf %d %s", __arglist(d, i, s));
Console.WriteLine(str.ToString());
Console.ReadKey();
}
}
Please don't use that.
uint is 32 bits. The length parameter of snprintf is size_t, which is 64 bits in 64 bit processes. Change the second parameter to IntPtr, which is the closest .NET equivalent of size_t.
In addition, you need to preallocate your StringBuilder. Currently you have a buffer overrun.
Try MarshalAs R8 (Thats for real/floating 8 (which is double)) on last parameter in second function.
Take a look at these two articles:
http://www.codeproject.com/Messages/2840231/Alternative-using-MSVCRT-sprintf.aspx (this is actually note to CodeProject article)
http://bartdesmet.net/blogs/bart/archive/2006/09/28/4473.aspx
Related
Am I doing it right? I have two projects in parallel, the first is code that was made in C ++ and the second project (Console made in AspNetCore v3.1) is the attempt to call the method that is in C ++ code.
I need to call the C ++ method "Decrypt" in the C # project. How do I do that?
C++ code
#include <stdlib.h>
#include <string.h>
#include<windows.h>
#include "bascript.hpp"
extern "C"
int FAR PASCAL _export
Decript( const LPSTR name, const LPSTR passwordWithCript,
LPSTR passwordWithoutCript, unsigned int sizeSpaceRetorn ) {
LPSTR result = lpDecript( name, passwordWithCript);
if ( sizeSpaceRetorn < strlen(result) )
return 0;
strcpy( passwordWithoutCript, result );
delete result;
return 1;
}
C#
class Program
{
[DllImport(#"C:\MS\VS\TesteDLLCentura\TesteDLLCentura\bin\Debug\netcoreapp3.1\Sises.DLL", CharSet = CharSet.Auto, EntryPoint = "Decript")]
private static extern string Decript(string name, string passwordWithCript, string passwordWithoutCript, uint sizeSpaceRetorn);
static void Main(string[] args)
{
string retorno = Decript("<user>", "<cript_password>", "", 0);
Console.WriteLine(retorno);
Console.ReadLine();
}
}
You can return a pointer from native world (C/C++, etc.) as long as you use a .NET compatible memory allocator. On Windows, that would be the COM Allocator.
So here are 3 ways to return a string: Ansi, Unicode and BSTR (unicode). Note: you should avoid using Ansi on Windows.
C++ side:
extern "C" __declspec(dllexport) void* DecryptA(const char* name, const char* password)
{
char str[] = "hello ansi world";
int size = (lstrlenA(str) + 1) * sizeof(char); // terminating zero
// use .NET compatible allocator
void* buffer = CoTaskMemAlloc(size);
CopyMemory(buffer, str, size);
return buffer;
}
extern "C" __declspec(dllexport) void* DecryptW(const wchar_t* name, const wchar_t* password)
{
wchar_t str[] = L"hello unicode world";
int size = (lstrlenW(str) + 1) * sizeof(wchar_t); // terminating zero
// use .NET compatible allocator
void* buffer = CoTaskMemAlloc(size);
CopyMemory(buffer, str, size);
return buffer;
}
extern "C" __declspec(dllexport) BSTR DecryptBSTR(const wchar_t* name, const wchar_t* password)
{
wchar_t str[] = L"hello BSTR world";
// use .NET compatible allocator and COM coolness
return SysAllocString(str);
}
C# side:
[DllImport("mydll", CharSet = CharSet.Ansi)]
private static extern string DecryptA(string name, string password);
[DllImport("mydll", CharSet = CharSet.Unicode)]
private static extern string DecryptW(string name, string password);
[DllImport("mydll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string DecryptBSTR(string name, string password);
...
static void Main()
{
Console.WriteLine(DecryptA("name", "password"));
Console.WriteLine(DecryptW("name", "password"));
Console.WriteLine(DecryptBSTR("name", "password"));
}
Your C++ function does not return a string or equivalent. It returns an int result (success or failure), and the actual result goes in the passwordWithoutCript buffer.
So you need to create the buffer and pass it in.
Because you are using LPSTR on the C++ side, you need CharSet.Ansi:
[DllImport(#"C:\MS\VS\TesteDLLCentura\TesteDLLCentura\bin\Debug\netcoreapp3.1\Sises.DLL", CharSet = CharSet.Ansi, EntryPoint = "Decript")]
private static extern int Decript(string name, string passwordWithCript, StringBuilder passwordWithoutCript, uint sizeSpaceRetorn);
static void Main(string[] args)
{
var sb = new StringBuilder(1000); // or whatever size
if(Decript("<user>", "<cript_password>", sb, sb.Length) == 1)
Console.WriteLine(retorno);
Console.ReadLine();
}
My program:
class Program {
[DllImport("libiconvD.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr libiconv_open([MarshalAs(UnmanagedType.LPStr)]
string tocode,
[MarshalAs(UnmanagedType.LPStr)]
string fromcode);
[DllImport("libiconvD.dll", CallingConvention = CallingConvention.Cdecl)]
static extern ulong libiconv(IntPtr icd,
ref StringBuilder inbuf, ref ulong inbytesleft,
out StringBuilder outbuf, out ulong outbytesleft);
[DllImport("libiconvD.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int libiconv_close(IntPtr icd);
static void Main(string[] args) {
var inbuf = new StringBuilder("Rule(s): Global Tag – Refer to Print Rules – General Requirements");
ulong inbytes = (ulong)inbuf.Length;
ulong outbytes = inbytes;
StringBuilder outbuf = new StringBuilder((int)outbytes);
IntPtr icd = libiconv_open("utf8", "windows-1252");
var rcode1 = libiconv(icd, ref inbuf, ref inbytes, out outbuf, out outbytes);
Debug.WriteLine(rcode1);
var rcode2 = libiconv_close(icd);
Debug.WriteLine(rcode2);
}//Main()
}//Program CLASS
The first call of libiconv_open() works and return a pointer to icd.
When the 2nd call of libiconv() runs it gets access violation on the icd pointer.
Here is the C code being called:
size_t iconv (iconv_t icd,
ICONV_CONST char* * inbuf, size_t *inbytesleft,
char* * outbuf, size_t *outbytesleft)
{
conv_t cd = (conv_t) icd;
if (inbuf == NULL || *inbuf == NULL)
return cd->lfuncs.loop_reset(icd,outbuf,outbytesleft);
else
return cd->lfuncs.loop_convert(icd,
(const char* *)inbuf,inbytesleft,
outbuf,outbytesleft);
}
It seems it can't access the function defined in the structure that pointer points to. Is there something special that has to be done to a returned pointer to make usable in subsequent calls.
Thanks
Turns out that using libiconv library is unnecessary with C#. Just use the Encoding class.
static void Main(string[] args) {
UTF8Encoding utf8 = new UTF8Encoding();
Encoding w1252 = Encoding.GetEncoding(1252);
string inbuf = "Rule(s): Global Tag – Refer to Print Rules – General Requirements";
byte[] bytearray = utf8.GetBytes(inbuf);
byte[] outbytes = Encoding.Convert(utf8, w1252, bytearray);
Debug.WriteLine("*************************");
Debug.WriteLine(String.Format(" Input: {0}", inbuf));
Debug.WriteLine(String.Format(" Output: {0}", utf8.GetString(outbytes)));
Debug.WriteLine("*************************");
}//Main()
*************************
Input: Rule(s): Global Tag – Refer to Print Rules – General Requirements
Output: Rule(s): Global Tag – Refer to Print Rules – General Requirements
*************************
I'm trying to connect a C# executable to a C++ dll. One of the methods of the dll receives a const char* and an int* (the first one specifying an input value, and the second one, an address to return a value):
extern "C" __declspec(dllexport)
int setVal(long handle, const char* ptrVal, int* ptrRet);
The first thing this function does is to check whether ptrVal is null, and returns -1 if so.
On the other hand, the C# code invokes the dll as follows:
[DllImport(dllName,
EntryPoint = "setVal",
ExactSpelling = true,
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
public static extern int setVal(long handle,
[MarshalAs(UnmanagedType.LPStr)] string str,
ref int ptrRes);
In the main function, I have
long handle = 0;
int result = 0;
int res = 0;
string str = "Hello World!";
result = setVal(handle, str, ref res);
When calling this function, I always receive a null pointer at the C side, which makes result equal to -1. I have tried different approaches when declaring the wrapper function, without success:
public static extern int setVal(long handle,
[MarshalAs(UnmanagedType.LPStr)] [In] string str,
[Out] int ptrRes);
public static unsafe extern int setVal(long handle,
[MarshalAs(UnmanagedType.LPStr)] string str,
ref int ptrRes);
public static extern int setVal(long handle,
StringBuilder sb,
ref int ptrRes); // also the unsafe version
public static extern int setVal(long handle,
byte[] value,
ref int ptrRes); // also the unsafe version
I'm using Visual Studio 2017, and .NET framework 4.6.1.
Why am I always receiving NULL as the second argument (const char*) of the dll function?
I did a bit of a google so this would be untested code but I can see it's something you haven't tried
declare external like so
[DllImport(dllName, EntryPoint = "setVal", ExactSpelling = true, CallingConvention, CallingConvention.Cdecl,CharSet = CharSet.Ansi)]
public static extern int setVal(int handle, StringBuilder sb, ref int ptrRes);
and use it like so
int handle = 0;
int result = 0;
int res = 0;
StringBuilder sb = new StringBuilder("Hello World");
result = setVal(handle, sb, ref res);
On Windows, the long handle in C/C++ is int handle in C# both at 32 and 64 bits. You can check it by doing a sizeof(long) in C/C++. Windows is LLP64.
I have a DLL wrote in C and I want to export the function :
__declspec( dllexport ) int GetDllVersion(char *Version);
and I have
class DllSolderMask
{
[DllImport("Dll.dll", CharSet=CharSet.Ansi)]
public static extern int GetDllVersion(ref string version);
}
static void Main(string[] args)
{
string strVersion;
DllSolderMask.AltixDllVersion(ref strVersion);
Console.WriteLine(strVersion.ToString());
}
The char *Version is obviously a character's array and so ref string strVersion could do the job but it doesn't work.
Ok I found out how to properly use StringBuilder :
[DllImport("AltixDll.dll", CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
public extern static int AltixDllVersion([MarshalAs(UnmanagedType.LPStr)] StringBuilder DllVersion);
StringBuilder str = new StringBuilder();
DllSolderMask.AltixDllVersion(str);
lblDll.Text = str.ToString();
the byte array way is not dynamic and it requires me to know the space I need in my array declaration, and it's not accurate.
Did you try to pass a byte[] like this :
byte[] strVersion = new byte[256];
DllSolderMask.AltixDllVersion(strVersion);
string myString = System.Text.Encoding.Default.GetString(strVersion);
Console.WriteLine(myString);
I think that from C++ point of view, char* is just a pointer to an array of 8-bit entries.
I'm having an issue where I get a different memory layout when debugging with ReSharper.
I have an unmanaged method that returns an array of (at most) 7-character, null-terminated strings. When executing this method without ReSharper's debugger, the start of the "next" string is 16 bytes later. When executing it with ReSharper's debugger (via ReSharper's Unit Test form choosing the "Debug Unit Tests" option), the start is 64 bytes later.
The method signature is similar to the snippet below. The string array is then "created" similar to the solution here.
[return: MarshalAs(UnmanagedType.I1)]
[DllImport("myDll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private static extern bool GetStrings(IntPtr sourceFile,
out IntPtr ptrToStrings,
out uint numberOfStrings);
Try using this to obtain the strings:
[return: MarshalAs(UnmanagedType.I1)]
[DllImport("myDll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private static unsafe extern bool GetStrings(IntPtr sourceFile,
[Out] out byte* ptrToStrings,
[Out] out uint numberOfStrings);
[SecuritySafeCritical]
private static unsafe string[] ManagedMethod(IntPtr sourceFile)
{
uint size;
byte* array;
if (!GetStrings(sourceFile, out array, out size))
{
throw new Exception("Unable to read strings.");
}
string[] retval = new string[size];
for (int i = 0, p = 0; i < size; i++, p += 8)
{
retval[i] = Marshal.PtrToStringAnsi(new IntPtr(&array[p]));
}
return retval;
}