I have two communicating components - one managed, the other unmanaged. The managed needs to retrieve a character string from the unmanaged implementation (the same string or just a copy). I tried the following code.
// Unmanaged code
const char* GetTestName(Test* test)
{
return test->getName();
}
// Managed wrapper
[DllImport(DllName, EntryPoint = "GetTestName")]
public static extern IntPtr GetTestName(IntPtr testObj);
// API Invocation
IntPtr testName = GetTestName(test);
string testStr = Marshal.PtrToStringAuto(testName);
But, the value of testStr is not what is expected. Does anyone know what I'm doing wrong here? Any suggestions would be really helpful.
You're close but you have to use PtrToStringAnsi(). Auto uses the system default which will be Unicode.
I'd suggest this, instead:
[DllImport(DllName, EntryPoint = "EntryPoint")]
[MarshalAs(UnmanagedType.LPStr)]
public static extern StringBuilder GetTestName(IntPtr testObj);
UnmanagedType.LPStr works with strings and System.Text.StringBuilder, and perhaps others (I only ever used those two). I've found StringBuilder to work more consistantly, though.
See this MSDN article for further information on the various string marshalling options.
Related
I need to implement this DLLImport in C#
const char* PegaSolicitacao(const char* CNPJ,
const char* CPF,
const char* CRM,
const char* UF_CRM,
const char* DT_EMISSAO );
The Dll could be found at this link
https://farmaciapopular-portal-homologacao.saude.gov.br/farmaciapopular-portal/gbas/GBASMSB_2-Client.rar
Inside the .RAR \GBASMSB_2-Client\Ofd SDK 0.2 Windows.zip -> gbasmsb_library.dll
The only way that i got a return was with this code:
[DllImport(#"gbasmsb_library.dll")]
public static extern char PegaSolicitacao(string CNPJ,
string CPF,
string CRM,
string UF_CRM,
string DT_Emissao);
var Teste = PegaSolicitacao("31617905000139",
"99999999484",
"30828",
"SP",
DateTime.Today.ToString("d"));
But the return is suposed to be a string not a char.
When I tried return a string in the DLLImport the system breaks, if I try to return a char[] I got a exception telling me about Marshaling.
Im, new at C# and never worked with MarshalAs, but looking at the Forum I tried some option like:
[DllImport(#"gbasmsb_library.dll", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPTStr)]
public static extern char[] PegaSolicitacao([MarshalAs(UnmanagedType.LPArray)]char[] CNPJ,
[MarshalAs(UnmanagedType.LPArray)]char[] CPF,
[MarshalAs(UnmanagedType.LPArray)]char[] CRM,
[MarshalAs(UnmanagedType.LPArray)]char[] UF_CRM,
[MarshalAs(UnmanagedType.LPArray)]char[] DT_Emissao);
and some other variants too, but I cant find the right option.
Using the DLL
I've used DLL Export Viewer to see the exported functions. A quick google search resulted in these C export definitions:
const char* IdentificaEstacao();
const char* PegaSolicitacao( const char* CNPJ, const char* CPF, const char* CRM, const char* UF_CRM, const char* DT_EMISSAO );
const char* PegaConfirmacao( const char* CNPJ, const char* NU_AUTORIZACAO, const char* NU_CUPOM_FISCAL );
Because of its simplicity, I decided to start with IdentificaEstacao which should return an identifier that identifies the station.
I've tried all kind of return MarshalAs, CharSet and CallingConvention values, but couldn't get it working with an import that returns a string type. So let's change the return type to IntPtr instead:
[DllImport("gbasmsb_library.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr IdentificaEstacao();
Now, when calling that function, you'll get back an IntPtr that points to a memory address. When checking the content of that memory location (Debug > Windows > Memory > Memory1 while pausing on a breakpoint), you can see a single-byte, null-terminated string (looks like Base64 data).
I tried freeing it with one of the Marshal.Free... methods, but that didn't work. I've called the same method several times and each time we're getting back the same memory address in the IntPtr which makes me guess that they're using a global allocated string that should not be freed by the caller (that might also be the reason why the string return type doesn't work).
With the code below, we're able to get the station identifier:
var ptr = IdentificaEstacao();
var stationIdentifier = Marshal.PtrToStringAnsi(ptr);
Let's change the signature of the other import in the same way:
[DllImport("gbasmsb_library.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr PegaSolicitacao(
[MarshalAs(UnmanagedType.LPStr)] string CNPJ,
[MarshalAs(UnmanagedType.LPStr)] string CPF,
[MarshalAs(UnmanagedType.LPStr)] string CRM,
[MarshalAs(UnmanagedType.LPStr)] string UF_CRM,
[MarshalAs(UnmanagedType.LPStr)] string DT_Emissao);
And make this test call:
var ptr = PegaSolicitacao("31617905000139",
"99999999484",
"30828",
"SP",
DateTime.Today.ToString("d"));
This again returns a pointer to a static string (calling it multiple times returns the same memory address), so you can just get the result by calling Marshal.PtrToStringAnsi(ptr); again.
As an additional test, I've ran this in a tight loop and there seems to be no memory leak, so this should be a pretty safe way of calling the imported function(s).
Note that I have changed the CallingConvention from StdCall to Cdecl so we can use string as input parameters without getting the unbalanced stack exception.
Using the EXE
I've also noticed that the archive contains an executable gbasmsb_gbas.exe which can perform the same functions.
gbasmsb_gbas.exe --i gives you the station identifier, while gbasmsb_gbas.exe --solicitacao 99999999484 31617905000139 30828 SP 12/03/2019 returns the request info.
Calling the EXE and parsing the output is also a possible integration path that is less prone to breaking changes for future updates of that external library.
I am working on a project where I need to PInvoke the secur32!AddSecurityPackageA function, but I am still learning the ins and outs of how to do this by hand and could use some help.
Here are a the references I am working with:
https://learn.microsoft.com/en-us/windows/desktop/api/sspi/nf-sspi-addsecuritypackagea
https://learn.microsoft.com/en-us/windows/desktop/api/sspi/ns-sspi-_security_package_options
And here's a sample of my code where I am trying to define the struct and call the function:
[DllImport("secur32.dll", EntryPoint = "AddSecurityPackageA")]
public static extern void AddSecurityPackageA(
ref string pszPackageName,
ref SECURITY_PACKAGE_OPTIONS[] Options
);
[StructLayout(LayoutKind.Sequential, CharSet =CharSet.Ansi)]
public class SECURITY_PACKAGE_OPTIONS
{
public ulong Size;
public ulong Type;
public ulong Flags;
public ulong SignatureSize;
public IntPtr Signature;
}
string dll = #"c:\temp\test.dll";
SECURITY_PACKAGE_OPTIONS[] pkgOpts = new SECURITY_PACKAGE_OPTIONS();
AddSecurityPackageA(ref dll, ref pkgOpts);
My questions are:
At lines 3 and 4, is this an appropriate use of ref and is this generally correct according to the MSDN docs?
At line 14, the C++ struct on MSDN has this as a void pointer, but while researching I found that the C# equivalent is an IntPtr. Is that correct or do I need to use unsafe?
In general, has anyone found any really good PInvoke tutorials outside of reading other people's code? I'm moving over from Python so it is quite a bit different and much of what I've found is either "draw a circle, draw the rest of the owl" or insanely lengthy MSDN documentation that makes a lot of assumptions.
Thank you!
Some comments:
Use the W function rather than the A function. You don't want to limit yourself to ANSI. This is a Unicode world.
The function has a return value. You must declare the function with a matching return value type. Presumably it is uint or int but you should check in the C++ header file.
ref string is wrong. It should be string.
ref SECURITY_PACKAGE_OPTIONS[] is wrong. It is not an array. It is a pointer to a struct. Since you declared SECURITY_PACKAGE_OPTIONS as a class, a reference type, you can replace ref SECURITY_PACKAGE_OPTIONS[] with SECURITY_PACKAGE_OPTIONS.
C++ unsigned long is 32 bits, so it should be uint in C#.
IntPtr is correct, but that leaves unresolved the question of how to declare the digital signature and obtain a pointer to it. I think it's outside the remit of this question for us to track down an example of how to do that.
This one works for me:
[DllImport("secur32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint AddSecurityPackage(
string pszPackageName,
SECURITY_PACKAGE_OPTIONS Options
);
[DllImport("secur32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint DeleteSecurityPackage(
string pszPackageName
);
I have an unmanaged C++ DLL that I have wrapped with a simple C interface so I can call PInvoke on it from C#. Here is an example method in the C wrapper:
const wchar_t* getMyString()
{
// Assume that someWideString is a std::wstring that will remain
// in memory for the life of the incoming calls.
return someWideString.c_str();
}
Here is my C# DLLImport setup.
[DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl )]
private static extern string GetMyString();
However the string is not correctly marshalled, often screwing up the first character or sometimes way off showing a bunch of chinese characters instead. I have logged output from the implementation on the C side to confirm that the std::wstring is correctly formed.
I have also tried changing the DLLImport to return an IntPtr and convert with a wrapped method using Marshal.PtrToStringUni and it has the same result.
[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl )]
private static extern IntPtr GetMyString();
public string GetMyStringMarshal()
{
return Marshal.PtrToStringUni( GetMyString() );
}
Any ideas?
Update with Answer
So as mentioned below, this is not really an issue with my bindings but the lifetime of my wchar_t*. My written assumption was wrong, someWideString was in fact being copied during my calls to the rest of the application. Therefore it existed only on the stack and was being let go before my C# code could finish marshalling it.
The correct solution is to either pass a pointer in to my method as described by shf301, or make sure my wchar_t* reference does not get moved / reallocated / destroyed before my C# interface has time to copy it.
Returning the std::wstring down to my C layer as a "const &std::wstring" means my call to c_str() will return a reference that won't be immediately dealloc'd outside the scope of my C method.
The calling C# code then needs to use Marshal.PtrToStringUni() to copy data from the reference into a managed string.
You are going to have to rewrite your getMyString function for the reasons mentioned in Hans Passant's answer.
You need to have the C# code pass a buffer in to your C++ code. That way the your code (ok, the CLR Marshaller) controls the lifetime of the buffer and you don't get into any undefined behavior.
Below is an implementation:
C++
void getMyString(wchar_t *str, int len)
{
wcscpy_s(str, len, someWideString.c_str());
}
C#
[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode )]
private static extern void GetMyString(StringBuffer str, int len);
public string GetMyStringMarshal()
{
StringBuffer buffer = new StringBuffer(255);
GetMyString(buffer, buffer.Capacity);
return buffer.ToString();
}
You need to specify MarshalAs attribute for the return value:
[DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
[return : MarshalAs(UnmanagedType.LPWStr)]
private static extern string GetMyString();
Make sure the function is indeed cdecl and that the wstring object is not destroyed when the function returns.
I have a problem, I tried to look at almost all the poster solutions, unsuccessful to find a suitable one.
The question is easy, Want to have a return string from unmanaged C code in my managed C#.
The c function is:
extern "C" __declspec(dllexport) int process_batch (char *&result);
and in C# I imported the DLL:
[DllImport("mydll.dll")]
public static extern IntPtr process_batch(StringBuilder result);
I run, but the return value in my StringBuilder is a 7-8 character string of non-sense! (I think the memory address)
I tried adding ref before the StringBuilder, this time I get the correct return value in StringBuilder but I get AccessViolationException :
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
So I need your help to fix this.
Just one more thing, I use malloc in c for allocating the memory to the char* variable.
Thanks,
If you really want to do that, pass the parameter as a ref IntPtr, then call Marshal.PtrToStringAnsi, or similar.
[DllImport("mydll.dll")]
public static extern IntPtr process_batch(ref IntPtr result);
Note that, since you're allocating the string with malloc in C, the .NET program will have to keep track of the returned pointer so that it can pass it back to you to be deallocated. Otherwise you'll have a memory leak.
If you were to pass a ref StringBuilder, you wouldn't be able to deallocate the memory that was allocated by the C program.
Also, as somebody commented in another post, you need to set the calling convention:
[DllImport("mydll.dll", CallingConvention=CallingConvention.Cdecl)]
I've been using the following code successfully:
[DllImport("user32.dll", EntryPoint = "GetClassName", ExactSpelling = false,
CharSet = CharSet.Auto, SetLastError = true)]
private static extern int _GetClassName(IntPtr hwnd, StringBuilder lpClassName,
int nMaxCount);
public static string GetClassName(IntPtr hWnd)
{
StringBuilder title = new StringBuilder(MAXTITLE);
int titleLength = _GetClassName(hWnd, title, title.Capacity + 1);
title.Length = titleLength;
return title.ToString();
}
I'd advise for a more specific declaration of the imported method via the DllImportAttribute. Try the CharSet = CharSet.Auto bit for instance
I know that this is not exactly related to your original problem as this makes use of the Windows API, but maybe it is of help nonetheless.
I am new in microsoft world.
I have lot of problem trying to pass a simple string from c# to dll/c++
I have read a lot of post and documentation about but the problem is the same.
C++ code
extern "C" __declspec(dllexport) int Init( long l , char* url );
C# code
[DllImport("MCRenderer.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = false)]
public static extern int Init(long a, StringBuilder url);
Init(hndl.ToInt64(), str );
what haeppen is that long value is passed correctly while string parameter is
0x00000000 <Bad Ptr>
can you help me ... Iam really confused
thanks!!
AG
You should pass a string, url should be of type string and not StringBuilder.
It's because you're marshalling incorrectly: long and int in C++ are both (usually) int in C#.
From MSDN, you need to:
The only caveat is that the StringBuilder must be allocated enough space for the return value, or the text will overflow, causing an exception to be thrown by P/Invoke
Also from Marshaling between Managed and Unmanaged Code
Don't pass StringBuilder by reference (using out or ref). Otherwise, the CLR will expect the signature of this argument to be wchar_t ** instead of wchar_t *, and it won't be able to pin StringBuilder's internal buffer. Performance will be significantly degraded.
Use StringBuilder when the unmanaged code is using Unicode. Otherwise, the CLR will have to make a copy of the string and convert it between Unicode and ANSI, thus degrading performance. Usually you should marshal StringBuilder as LPARRAY of Unicode characters or as LPWSTR.
Always specify the capacity of StringBuilder in advance and make sure the capacity is big enough to hold the buffer. The best practice on the unmanaged code side is to accept the size of the string buffer as an argument to avoid buffer overruns. In COM, you can also use size_is in IDL to specify the size.
So in your example, you need to specify the native size as the parameter of the StringBuilder like this StringBuilder str = new StringBuilder(SIZE_OF_NATIVE_STRING);
Try using LPCSTR in the dll function parameter
extern "C" __declspec(dllexport) int Init( long l , **LPCTSTR** url );
Here is a good example that I found on how to do this.
C++:
extern "C" __declspec(dllexport) void doSomething(LPCTSTR asString)
{
std::cout << "here" << (wchar_t)asString << endl;
system ("pause");
}
C#:
class Program
{
static void Main(string[] args)
{
String myString = "String in C#";
doSomething(myString);
}
private const String path = #"C:\testdll2.dll"; //Make sure that the DLL is in this location
[DllImport(path)]
private static extern void doSomething(string asString);
}