How do I marshal cstring* via P/Invoke? - c#

I'm attempting to call a 3rd-party DLL from C#, and I'm having trouble marshalling some string data. The DLL was written with Clarion, and I'm not very familiar with the data types being used. Specifically, the spec has this signature for a function that I can't get to work:
Bool QuerySoftwareVersion( cstring* version) // edited documentation typo
//Returns Software version (20 character cstring).
I was assuming that the cstring was just a null-terminated string, but I wasn't able to get it to work with out char[] version in my definition. Anyone know the right way to handle this?
EDIT: Actually, what I've found thus far suggests that a cstring in Clarion is indeed just a null-terminated string.
UPDATE: I've updated the title of the question and the details because it turns out that there was a typo in the DLL documentation. The version parameter in question is declared as type cstring* not cstringt*. And in Clarion,cstring` is apparently just a c-style, null-terminated string. So the marshalling shouldn't be that complicated, since they claim it was written with C calling conventions. Has anyone successfully p/invoked a Clarion DLL that passes strings via a reference param?

I had some success with a CString reference in a DLL API:
SCODE WINAPI Test( const CString& str );
I used the following C# code to import:
[DllImport("CBData.Dll")]
public static extern int Test( [MarshalAs(UnmanagedType.LPStr)] ref String str );
And this C# code to call:
String b = "Some text";
int x = Test(ref b);
This worked for me - I am not sure if this is safe though. I hope this helps you.

I've never called into Clarion, but I've got a DLL (in C) that is called from both Clarion and C#, and I'd interop that as:
[DllImport("clarionlib.dll", CharSet=CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl,
ExactSpelling=true, EntryPoint="QuerySoftwareVersion")] static extern
bool QuerySoftwareVersion(StringBuilder sName);
Note also that the StringBuilder parameter you pass has to be sized up to the maximum expected return size (pardon my C#, I'm sure there's a shorter equivalent):
System.Text.StringBuilder buffer;
buffer = new System.Text.StringBuilder();
buffer.EnsureCapacity(21);
QuerySoftwareVersion(buffer);
string myString = buffer.ToString();

Try with a StringBuilder:
[DllImport("mylibrary.dll")]
static extern bool QuerySoftwareVersion([Out] StringBuilder version);

Related

When calling c++ function from windows form gets error

My c++ function is given below
# define MyFunction _declspec(dllexport)
extern "C" {
MyFunction int SubtractNumbers(int a, int b)
{
return a - b;
}
MyFunction const char* GetStringKey()
{
return "My String";
}
}
Calling c++ function from windows form is given below,
[DllImport(cppFunctionsDll, CallingConvention = CallingConvention.Cdecl)]
private const string DllFilePath = #"D:\\Projects\\December\\17-12-2020\\project-device-
setup\\Debug\\AccurynCPP.dll";
[DllImport(DllFilePath, CallingConvention = CallingConvention.Cdecl)]
public static extern int SubtractNumbers(int a, int b);
[DllImport(DllFilePath, CallingConvention = CallingConvention.Cdecl)]
public static extern string GetStringKey();
public void GetSubtract()
{
lblSubtract.Text= Convert.ToString(SubtractNumbers(1, 2));
}
public void GetStringFunction()
{
lblstringKey.Text= GetStringKey();
}
From the above function, GetSubtract() worked perfectly. But GetStringKey() not worked. when it reach on it's function while debugging, it automatically cancelled running mode in visual studio. How can i fix this issue.
The calling convention describes to the compiler and linker how parameters, the stack and registers are to be handled. So if these don't match things won't work or memory can be corrupted. For instance, if the caller thinks the first parameter should be passed in the register eax, but the callee thinks it's on the stack. Things will obviously not work. For more info on calling conventions Wikipedia has a good description here. The calling convention you choose isn't very important, unless the compiler forces one on you. For instance for 64 bit programs Microsoft only uses one to my knowledge. So, I suggest adding __cdecl in front of the functions you have written. And using CallingConvention.Cdecl on the DLLImport lines.
Also, all the data types must exactly match. For instance:
[DllImport(DllFilePath, CallingConvention = CallingConvention.Cdecl)]
public static extern string GetStringKey();
This says it is returning a string, however despite our using the term string to represent a series of characters followed by a null. In this case string is a formal type. Which is they type std::string. Which is not what is being returned by the C code. The C code is returning a pointer to a char.
Generally passing complex types based on a library is kinda iffy. That's because it requires both languages to use the exact data structure to represent the type. So when crossing language boundaries it's often useful to stay with your own custom structures and very primitive types.
So I recommend your C function be this instead:
__cdecl bool GetStringKey(char *buffer, int maximumLength)
{
// Write code here to copy all the characters from your "My String"
// to the buffer or use a standard library function for copying char * strings (often called C strings) when copying make sure to copy the null character at the end. It's definitely needed.
return true; // Yes it fit
}
You will need to change your DllImport to match. Once that is working you can add code to never go over the maximum buffer length (to prevent memory corruption) and maybe change the bool to the length of the string or -1 for failure.
I suggested this particular function prototype because it doesn't pass memory between languages. Instead the C code fills in a buffer from the C++ code. Which is the safest way to do something. Passing memory between unrelated modules (in this case C and C++ doesn't always work). Though I suspect in this case your C and C++ compiler and libraries are enough alike. That you could just switch to returning a std::string. As long as both sides match perfectly.

can native C **char be safely passed to managed C# delegate with paramter type "out string"?

In C:
extern "C" __declspec(dllexport) int CfgGetVariableString(const char *Name, char **Value)
{
char StrValue[STR_MAX];
int RetValue = GetVariableToStrValue(Name, StrValue, STR_MAX);
if (RetValue == 0)
*Value = StrValue;
return RetValue;
}
C#
[DllImport(DllName, CallingConvention = DllCallingConvention)]
private static extern int CfgGetVariableString([MarshalAs(UnmanagedType.LPStr)]string name, [MarshalAs(UnmanagedType.LPStr)]out string value);
This does not work. I can make it work by calling CoTaskMemAlloc, but then I guess I should free it with separate managed to native call?
So what is the cleanest way to do it?
I can make it work by calling CoTaskMemAlloc.
That is the only way that it can work. Because the marshaller will call CoTaskMemFree to deallocate the memory returned from the native function.
I guess I should free it with separate managed to native call?
As mentioned above, that's not necessary because the framework already does so.
What is the cleanest way to do it?
In my opinion the cleanest approach here, since you are already using a string length determined at compile time, is to have the caller allocate the memory. Change the type from char** to char*. Add an extra argument containing the length of the allocated string. Use StringBuilder on the C# side. There are countless examples of this pattern available here and indeed in other places so I don't feel any need to add to the canon.

Using C++ native dll in .Net External component has thrown an exception

I'm trying to use a native C++ dll in C# and am getting the "External component has thrown an exception" with an error code of -2147467259.
C#:
[DllImport(#"MyDLL.dll", CharSet = CharSet.Auto)]
public static extern string MyFunction([MarshalAs(UnmanagedType.LPStr)] StringBuilder input);
C++:
__declspec(dllexport) char * __stdcall MyFunction(const char* inputPtr);
The function works just fine in C++. How can I track this error down?
I have tried using string and string builder for the parameter.
Update
I found this article http://tom-shelton.net/index.php/2008/12/11/creating-a-managed-wrapper-for-a-lib-file/
It details a way to use managed C++ to wrap an unmanaged static library in C++ which can then be used in a managed language. Would this be a good way of going about tackling this problem? Can the lib be an unmanaged dll?
try the technique from http://blog.rednael.com/2008/08/29/MarshallingUsingNativeDLLsInNET.aspx - this has saved the day several times :-)
If the parameter is in-only (and not out), stringbuilder is not required. If it's an out parameter (or ref) you should use stringbuilder and pre-allocate the buffer using stringbuilder constructor.
I can guess the problem is that you are returning an Ansi string instead of the expected unicode string. This cause the default pinvoke marshaler to read too much memory.
Try this:
[DllImport(#"MyDLL.dll", CharSet = CharSet.Auto)]
[return : MarshalAs(UnmanagedType.LPStr)]
public static extern string MyFunction([MarshalAs(UnmanagedType.LPStr)] string input);
In any case, in the vast majority of times, it does not make any sense to write Ansi C++ code. I would suggest to convert the C++ code to unicode only (not tchar but wchar_t).
CharSet = CharSet.Auto?? use CharSet.Ansi
You cannot use a char array as return, a block of memory allocated with c++ cannot be used by C#! In your case you probably need to copy it into C# managed memory.
Use StringBuilder for that or MarshalAs attribute that will copy for you the buffer into C# managed memory.
You have to change your C++ function, your C++ function must write into the destination buffer that must be allocate so it contains at least the number of characters you need plus one (for null termination character).
[DllImport(#"MyDLL.dll", CharSet = CharSet.Ansi)]
private static extern void MyFunction([MarshalAs(UnmanagedType.LPStr)] string input, StringBuilder result);
public static string MyFunctionPublic(string input)
{
StringBuilder sb = new StringBuilder(input.Length + 1);
MyFunction(input, sb);
return sb.ToString();
}
And i expect your C function to do something like this:
void __stdcall MyFunction(const char* input, char* result)
{
strcpy(result, input); // this is a dummy stupid code to show how it works.
}
Probably you will need a function that gives you idea of how much bytes you have to allocate in C#. This function can be something like this:
int __stdcall ComputeMyFunctionBytes(const char* input)
{
return strlen(input); // this is a dummy stupid code to show how it works.
}
Never return a piece of allocated memory to C#, it cannot do anything with that, nor deallocating, nor reading and nor writing, until you use unsafe code, but this is not what you are trying to do.
A good article seems to be here: http://www.devx.com/dotnet/Article/6990/1954
Also note that C# uses unicode, 16 bit per character, so it will be converted from ansi to unicode.

How would you declare DLL import signature?

this is a follow-up post of Using pHash from .NET
How would you declare following C++ declaration in .NET?
int ph_dct_imagehash(const char* file,ulong64 &hash);
So far i have
[DllImport(#"pHash.dll")]
public static extern int ph_dct_imagehash(string file, ref ulong hash);
But I am now getting following error for
ulong hash1 = 0, hash2 = 0;
string firstImage = #"C:\Users\dance2die\Pictures\2011-01-23\177.JPG";
string secondImage = #"C:\Users\dance2die\Pictures\2011-01-23\176.JPG";
ph_dct_imagehash(firstImage, ref hash1);
ph_dct_imagehash(secondImage, ref hash2);
It basically says that my declartion of ph_dtc_imagehash is wrong.
What am I doing wrong here?
Stack imbalance indicates that the C++ code uses cdecl and your C# uses stdcall calling convention. Change your DLLImport to this:
[DLLImport(#"pHash.dll", CallingConvention=CallingConvention.Cdecl)]
The function signature in C# (return value and parameters) is otherwise correct.
Check the calling convention. If you don't specify one on the DllImport attribute, it defaults to WinApi (stdcall). The C snippet you posted doesn't specify a calling convention, and at least in VC++ the default calling convention is cdecl.
So you should try:
[DllImport(#"pHash.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int ph_dct_imagehash(string file, ref ulong hash);
Try explicitly setting DllImportAttribute.CharSet property to CharSet.Auto as if you don't specify it, it will default to Ansi. This may be causing issues as your declaration seems to be fine.
Even if this is not the issue, take habit of specifying the CharSet property whenever a Dll function deals with text.

Returning a std::string from a C++ DLL to a c# program -> Invalid Address specified to RtlFreeHeap

In a function in my C++ DLL, I'm returning a std::string to my c# application. It pretty much looks like this:
std::string g_DllName = "MyDLL";
extern "C" THUNDER_API const char* __stdcall GetDLLName()
{
return g_DllName.c_str();
}
But when my C# code calls this function, I get this message in my output window:
Invalid Address specified to RtlFreeHeap( 00150000, 0012D8D8 )
The function declaration in c# looks like this:
[DllImport("MyDll", EntryPoint = "GetDLLName")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string GetDLLName();
From what I've been able to find online, sometimes this message appears when there's an inconsistency between which version of new(debug or release, etc) is being used with delete. But I'm not sure if that is what is going on in my case. So I'm not sure exactly what's causing it. Maybe the MashallAs might have something to do with it?
Any ideas?
Thanks!
I managed to find the issue. It was the way the C# definition was done. From what I can understand, using the MarshallAs(UnmanagedType.LPStr) in combination with the string return type makes it so that it'll attempt to free the string when its done. But because the string comes from the C++ DLL, and most likely a totally different memory manager, it fails. And even if it didn't fail, I don't want it to be freed anyway.
The solution I found was to change the C# declaration to this (the C++ code is unchanged):
[DllImport("MyDll", EntryPoint = "GetDLLName")]
public static extern IntPtr GetDLLName();
So this makes it so that it just returns a pointer to the string data. And then to change it to a string, pass it to Marshal.PtrToStringAnsi()
return Marshal.PtrToStringAnsi(GetDLLName());
And that gets wrapped into another function for cleanliness.
I found the solution from this page:
http://discuss.fogcreek.com/dotnetquestions/default.asp?cmd=show&ixPost=1108

Categories

Resources