C# DLLImport convert "const char*" to string - c#

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.

Related

What is the difference between out string and StringBuilder for .net/native marshalling?

I have a C library that I don't have the source code for that I need to call into from .NET.
The signature of the Query function I need is:
typedef int LoginID;
typedef const char* XML;
typedef DSQUERYHANDLER* PDSQUERYHANDLER;
typedef ErrCode DSQUERYHANDLER (LoginID lh, void* tag, XML resXML, ErrCode retCode);
Query(LoginID lh, XML queryXML, XML* queryResultXML_out, void* tag, PDSQUERYHANDLER cb);
The C# that I've written to correspond to this is:
delegate ErrCode DSQUERYHANDLER(int lh, IntPtr tag, string resXML, ErrCode retCode);
[DllImport("dllname.dll")]
internal static extern ErrCode Query(int lh, string queryXML, out string queryResultXML_out, IntPtr tag, DSQUERYHANDLER cb);
I don't actually care about the last two parameters and pass IntPtr.Zero and null.
When I call Query, it sets the string that I've given it with the value that I'd expect, but it also crashes with a System.AccessViolationException!
If I change to using StringBuilder instead of out string:
[DllImport("dllname.dll")]
internal static extern ErrCode Query(int lh, string queryXML, StringBuilder queryResultXML_out, IntPtr tag, DSQUERYHANDLER cb);
Then it doesn't crash, but the StringBuilder contains a length of 4, but with different values each time it's run, e.g Èf or p'F.
My Questions
What is the difference between using out string and StringBuilder? Also is it possible for me to debug this without access to the internals of the C library?
Your problem has been answered here -
DLLImport c++ functions with char* input as and output parameters
I won't repeat information here - please read the above link and this will explain the process for calling your function.
I feel explaining the difference between StringBuilder and String will not help you regarding your issue.

C# wrapper class for c++ lib dll

I am trying to create a class in c# to access the function in a c++ lib. The function in the c++ dll :
bool WriteReply(const unsigned char *reply, const unsigned long reply_length).
A sample of how its used in c++:-
unsigned short msg_id = 0x0000;
byte msg_body[] = {(byte)(GetTickCount()/0x100)}; // a random value for loopback data
// combine the message id and message body into an big msg
unsigned long msg_length = sizeof(msg_id)+sizeof(msg_body);
byte* big_msg = new byte[msg_length];
big_msg[0] = LOBYTE(msg_id);
big_msg[1] = HIBYTE(msg_id);
memcpy((void*)&big_msg[2], (void*)msg_body, sizeof(msg_body));
// send the big message
if (!big_dev.WriteReply(big_msg, msg_length))
{
//do something here
}
I can't seem to pass the function from c# to the dll (AccessViolationException). This is the command i've tried:-
byte[] bytearray = new byte[3] { 0x01, 0x02, 0x03 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(bytearray.Length);
Marshal.Copy(bytearray, 0, unmanagedPointer, bytearray.Length);
bool writestatus = (bool)NativeMethods.WriteReply(unmanagedPointer, (uint)bytearray.Length);
and on the import side:-
[DllImport("dllname.dll", EntryPoint = "WriteReply")]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool WriteReply(IntPtr msg, uint reply_length);
Please let me know where have i gone wrong?Thanks!
Assuming your C++ method uses the string and does not modify it...
Try this
__declspec(dllexport) bool __cdecl WriteReply(const unsigned char *reply, const unsigned long reply_length);
[DllImport("libfile.dll", EntryPoint = "WriteReply")]
private static extern bool WriteReplyExternal(
[MarshalAs(UnmanagedType.LPStr)] [Out] string replyString,
[Out] UInt32 replyLength);
Or better yet (since C strings are null-terminated and the buffer is readonly, so you don't have to worry about buffer overflow, the length parameter is redudant):
__declspec(dllexport) bool __cdecl WriteReply(const unsigned char *reply);
[DllImport("libfile.dll", EntryPoint = "WriteReply")]
private static extern bool WriteReplyExternal(
[MarshalAs(UnmanagedType.LPStr)] [Out] string replyString);
These will work if the method is not within a class, otherwise you will need to use the C++ mangled name as the entry point.
If your string contains characters outside the 1...127 ASCII range (e.g. non-English letters), you should use wchar_t instead of char in the C++ and LPWStr instead of LPStr in the marshalling.
Edit:
You need to wrap the private method with another method with a signature that is more appropriate for .NET e.g.
public void WriteReply(string message)
{
var result = WriteReplyExternal(message, message.Length);
if (result == false)
throw new ApplicationException("WriteReplay failed ...");
}
I think the latest addition of code provides a clue as to the real problem:
if (!big_dev.WriteReply(big_msg, msg_length))
This cannot work because WriteReply is an member function. You need to be calling a C style function rather than a C++ member function. The latter requires an instance (big_dev in the code sample).

Return contents of a std::wstring from C++ into C#

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.

Receiving a char* from c++ into c#, and passing it back again

I'm having memory leak issues with a third party c++ dll. For certain calls, the dll allocates memory for the string, passes it out as a char* and then expects to receive that pointer back so that it can de-allocate the memory.
Here are some comments from the header file, a couple of examples of where the char* get returned, and the signature of the "Release" method.
(The dll is called SW_API, it's from a trade clearing house - if anyone has perhaps wrapped this already I'd love to talk to them!).
/* Strings returned by the API are similarly normal nul-terminated C strings.
* The user should not attempt to change any of the bytes or read past the
* terminating nul of any returned string. All returned strings must be
* released using SW_ReleaseString() once the user is finished with the
* result. Failure to do this will result in memory leaks.
*/
/**
* #typedef const char* SW_XML
* #brief A string containing an XML documents text.
* #note As with all output strings, returned XML must be freed
* by the user. See #ref resource.
* #sa ErrorCodes
*/
typedef const char* SW_XML;
const char* STDAPICALLTYPE SW_GetLastErrorSpecifics();
SW_ErrCode STDAPICALLTYPE SW_DealGetSWML(SW_LoginID lh,
const char* swmlVersion,
SW_DealVersionHandle dealVersionHandle,
SW_XML* resultXML_out);
void STDAPICALLTYPE SW_ReleaseString(const char* buffer);
Attempting to read up from various sources, I have tried the following:
// Extern declarations
[DllImport(sw_api_dll, EntryPoint = "_SW_GetLastErrorSpecifics#0", CharSet = CharSet.Ansi)]
public static extern IntPtr SW_GetLastErrorSpecifics();
[DllImport(sw_api_dll, EntryPoint = "_SW_DealGetSWML#16", CharSet = CharSet.Ansi)]
public static extern int SW_DealGetSWML(int lh, string swmlVersion, string dealVersionHandle, [Out] out IntPtr outputSWML);
[DllImport(sw_api_dll, EntryPoint = "_SW_ReleaseString#4", CharSet=CharSet.Ansi)]
public static extern void SW_ReleaseString(IntPtr buffer);
// Using the externs.
private static string GetIntPtrStringAndRelease(IntPtr ptr)
{
string result = Marshal.PtrToStringAnsi(ptr);
API.SW_ReleaseString(ptr);
return result;
}
public static int SW_DealGetSWML(int lh, string swmlVersion, string dealVersionHandle, ref string outputSWML)
{
IntPtr outputSWML_out = new IntPtr();
int result = API.SW_DealGetSWML(lh, swmlVersion, dealVersionHandle, out outputSWML_out);
outputSWML = GetIntPtrStringAndRelease(outputSWML_out);
return result;
}
public static string SW_GetLastErrorSpecifics()
{
IntPtr ptr = API.SW_GetLastErrorSpecifics();
return GetIntPtrStringAndRelease(ptr);
}
It seems I just can't get the API to release the strings.
Now, it's possible that this is just a bug in the API, but I doubt it.
More likely is I'm doing something funamentally wrong.
All I know is that my working set just keeps on growing.
The company in question provide a Java wrapper but won't stretch to a .Net wrapper.
Any help most gratefully received.
Brett.
My best guess is that the IntPtr is not equivalent to the char* of your string. So when you call SW_ReleaseString, you're not providing the same pointer.
What you can do, is throw together a little C++CLI intermediary. In C++CLI, and you will have access to the char* directly, as well as being able to use Marshal::PtrToString and managed string pointers, with String^.
Here's what I think that would look like:
C++/CLI:
String^ GetStringAndRelease(char* ptr)
{
string result = Marshal::PtrToStringAnsi(ptr);
SW_ReleaseString(ptr);
return result;
}
int SW_DealGetSWML(int lh, const char* swmlVersion, const char* dealVersionHandle, String% outputSWML)
{
char* outputSWML_out;
int result = SW_DealGetSWML(lh, swmlVersion, dealVersionHandle, outputSWML_out);
outputSWML = GetStringAndRelease(outputSWML_out);
return result;
}
String^ SW_GetLastErrorSpecifics()
{
char* ptr = SW_GetLastErrorSpecifics();
return GetStringAndRelease(ptr);
}
and then in C#:
[DllImport(your_wrapper_dll, EntryPoint = "_SW_DealGetSWML#16", CharSet = CharSet.Ansi)]
public static extern int SW_DealGetSWML(int lh, string swmlVersion, string dealVersionHandle, [Out] out string outputSWML);
[DllImport(your_wrapper_dll, EntryPoint = "_SW_GetLastErrorSpecifics#0", CharSet = CharSet.Ansi)]
public static extern string SW_GetLastErrorSpecifics();
I'm a C# guy, not a C++ guy, but in the unmanaged C++ DLLs that I work with that use char* parameters I marshall them as StringBuilders. I did read somewhere that for const char* the best choice is System.String but StringBuilder for char *. However, if you need to keep the pointer so you can send it back to release the memory maybe StringBuilder would work better since System.String is immutable?

Passing a const char* character string from unmanaged to managed

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.

Categories