Passing string from .NET to native (64-bit) - c#

I am having an issue passing a simple string from my .NET app, compiled as 64-bit, to my native DLL, also compiled as 64-bit.
C++ signature:
DllExport void SetFoo(LPWSTR foo);
C# signature:
[DllImport(Library, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
internal static extern void SetFoo(
[In][MarshalAs(UnmanagedType.LPWStr)] string foo);
Then I run the following code in C#:
X.SetFoo("some string");
When it reaches the debugger in the DLL, the value is swearing at me in Chinese: Ⴐ虘翺
When I change both the .NET and native code to 32-bit, the value I get in the debugger is the correct string. Where am I being stupid?
Minimal reproduction as a Visual Studio 2015 Solution: download here
To reproduce:
Create a new Visual Studio solution with a WinForms project.
Add a Win32 Project, of type DLL to the solution
Add the following files:
Foo.h:
extern "C" {
__declspec( dllexport ) void SetFoo(LPWSTR);
}
Foo.cpp:
#include "stdafx.h"
#include "Foo.h"
DllExport void SetFoo(LPWSTR foo)
{
}
Set a breakpoint on the opening brace in SetFoo
Add a button to the winforms form
Double click it, and call SetFoo("abc123").
Implement SetFoo:
Form1.cs:
[DllImport("Win32Project1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void SetFoo(string text);
Change the apps to build in 64-bit mode by opening Configuration Manager.
Set Win32Project1 to build as x64
Set WindowsFormApplication1 to build as x64, by picking platform new, pick x64, OK.
Change the output directory of WindowsFormsApplication1 to match the output directory of the other app.
Start without debugging.
Attach debugger (Ctrl+Alt+P) by setting Attach to to Managed (v4.5, v4.0) code, Native code and finding the process.
Observe value at breakpoint.

When you interpret ANSI encoded latin text as UTF-16 you see Chinese characters. That's clearly what is happening. So your C# code is sending ANSI encoded text somehow.
The p/invoke would be better written like this:
[DllImport(Library, CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode)]
internal static extern void SetFoo(string foo);
The [in] is not needed, it is the default. And the MarshalAs is pointless since you specified CharSet.Unicode. However, neither change affects the semantics.
The only sound explanations for what you describe in the question are:
The actual code is not as you have described it, or
There is a defect in the debugger.
I suggest that you change the unmanaged function to
DllExport void SetFoo(LPWSTR foo)
{
MessageBoxW(0, L"", foo, MB_OK);
}
If the message box displays the correct text then the conclusion would appear to be that the debugger is defective.

Related

Interfacing both Debug and Release DLLs with the same C# code in SWIG?

SWIG lets you specify the DLL to import in C# by using the -dllimport command line argument.
What about importing a DLL whose name depends on whether it is a Debug version or a Release one? This happens with DLLs that follow the Microsoft convention of appending the d suffix to the Debug version, e.g. ucrtbase.dll for the Release version, and ucrtbased.dll for Debug.
If -dllimport allowed to specify a symbolic constant, then the value of such constant could depend on whether DEBUG is defined or not, but that does not seem to be the case.
I think you can do what you want using a static constructor and the SetDllDirectory of suggestion of this this answer.
To test this out I put together this example:
%module foobar
%pragma(csharp) imclasscode=%{
[global::System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
static $imclassname() {
if (isDebug)
SetDLLDirectory("path/to/directory/with/debugDll");
else
SetDLLDirectory("path/to/directory/with/regularDll");
}
%}
You have to build it with SWIG like this in order to suppress the default static constructor:
swig3.0 -DSWIG_CSHARP_NO_IMCLASS_STATIC_CONSTRUCTOR -csharp test.i
Quite how you'll distinguish isDebug in real code I'm less clear on - maybe use this to find the current HMODULE and then GetModuleFilename to see if it's your debug build or not.

embedding c++ native dll in set up

I want to embed c++ native dll in set up file created with install shield limited edition.
Hint :- My application created by using c# and c++ native dll.
Here is my example :-
My c++ dll_code
extern "c" __declspec(dllexport) int function_c ()
{
int a=10;
return a;
}
My .net code
public partial class Form1 : Form
{
[DllImport(#"C:\Users\bajwa\Documents\Visual Studio 2012\Projects\c++dll\c++_dll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern int function_c();
void csharp_function()
{
int result= function_c(); // calling c++ native function
MessageBox.Show(result);
}
private void button1_Click(object sender, EventArgs e)
{
csharp_function(); // calling c# function.
}
}
When I installed this setup on my computer it runs perfectly. Because C++ native dll is placed on my computer at "C:\Users\bajwa\Documents\Visual Studio 2012\Projects\c++dll\c++_dll.dll".
But when I delete the c++ native dll from that location then it shows the error.
dll not fount at this location
Please help and solve my problem.
I think you will just need to use a relative path for the imported DLL. The easiest thing to do would be to copy the DLL to a known folder relative to the executable (maybe even the same folder) and then use that path. So if they share a folder, your code can become this:
[DllImport("c++_dll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern int function_c();
Then just modify the installer to place the dll in the correct location. To test, just change your code and move the dll over, but it should work.

pass C# string to C++ dll fails in Release build

I have a C++ dll with simple function like
#ifdef BUILDING_THE_DLL
#define EXPORTED __declspec(dllexport)
#else
#define EXPORTED __declspec(dllimport)
#endif
...
EXPORTED void AddSetting(char *key, char *value)
And a C# project with function declaration:
[DllImport("pers.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void AddSetting(string key, string value);
Everything works perfectly well while C# project is build in Debug mode. In Release build an exception is fired: "An attempt was made to load a program with an incorrect format."
Any ideas?
UPD: In C# project Platform target was set to x86 in Debug mode and Any CPU in Release. I've changed to x86 in Release and it was the solution. Thanks a lot to Matt.
You need specify to use the ansi format of the string, by default. it is unicode. It should be :
[DllImport("pers.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet::Ansi)]
public static extern void AddSetting(string key, string value);
Also, if the function "AddSetting" modifies the strings, you need to use StringBuilder in C#. Please refer this MSDN article for details.

Function Parameters for LabVIEW DLL

I am trying to call a function from a DLL generated in LabVIEW. I thought this was going to be far more straightforward than it is turning out to be. The function is described below:
void __cdecl Device_Init(char DevName[]);
So in my C# code I am trying the following:
[DllImport(#"Device.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void Device_Init(StringBuilder name);
I call this in my application by simply using the following:
StringBuilder devName = new StringBuilder(DeviceName);
Device_Init(devName);
Rather than getting any initialization on my device, I see a LabVIEW vi window pop up that has a title akin to a different method within the dll (i.e. AF1_GetPressure.vi). The application then hangs with this LabVIEW window popped up and I have to exit the debugging session.
I guess my question is how my function signature might be erroneous... I used StringBuilder as I found an example on the NI website that seemed to indicate that LabVIEW requires this variable type to better ascertain the number of characters in the array. http://www.ni.com/example/31050/en/
I have tried all kinds of different combinations of parameter types but I simply can't seem to get this to work. If I try calling the dll from C++ then I can get things to work. Although, oddly, I had to dynamically load the dll in C++ because I was getting a dll initialization failure when I tried to load it with the application.
Any help would be greatly appreciated!
I was able to build a DLL with LabView 2012, and import it into a .NET 4.0 console application, call the function, and receive a result. Here is a screenshot of the VI:
And here is the import statement in C#:
[DllImport(#"SharedLib.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int StringLength(string str);
I would recommend trying something very simple like this and see if you can get it working.
I should note that I tried passing my parameter as a StringBuilder object and that worked as well - and I didn't expect it to!
Also, I recommend posting this question on the LabView forums. I was always able to get a very quick response there, and I think with LabView, you're likely to get a better response there than StackOverflow.
As requested, here are the contents of the .h file generated by LabView:
#include "extcode.h"
#pragma pack(push)
#pragma pack(1)
#ifdef __cplusplus
extern "C" {
#endif
/*!
* StringLength
*/
int32_t __cdecl StringLength(char String[]);
long __cdecl LVDLLStatus(char *errStr, int errStrLen, void *module);
#ifdef __cplusplus
} // extern "C"
#endif
#pragma pack(pop)

Calling C++ DLL from C# within VS fails. Run exe outside VS and it works

I have a C++ DLL that is being called like below from a C# console app.
When run in Visual Studio to debug it, it throws an exception saying the stack is unstable and to check that the method arguments are correct. However, if I run the *.exe outside of VS from Windows Explorer it retuns data to the screen as expected.
How can I get this to run within Visual Studio?
Thanks
**From the C++ header file:**
#ifdef RFIDSVRCONNECT_EXPORTS
#define RFID_CONN_API __declspec(dllexport)
#else
#define RFID_CONN_API __declspec(dllimport)
#endif
RFID_CONN_API BSTR rscListDevices( long showall ) ;
[DllImport("MyDLL.dll")]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string rscListDevices(int showall);
static void Main(string[] args)
{
string data= rscListDevices(0);
Console.WriteLine(data);
Console.ReadKey();
}
Firstly, make sure you're using the same calling convention in both C++ and C#.
I suspect that the /Gd compiler option is set (since it is set by default), so __cdecl is used as default calling convention for unmarked functions.
You can fix crashes by either specifing the same calling convention in your C# code:
[DllImport("MyDLL.dll", CallingConvention=CallingConvention.Cdecl))]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string rscListDevices(int showall);
Or changing rscListDevices's calling convention to __stdcall (which is the default in C#):
RFID_CONN_API BSTR __stdcall rscListDevices( long showall ) ;
You can also set __stdcall as the default calling convention for the unmarked functions in your C++ DLL by changing compiler option from /Gd to /Gz in manually or using the Project Properties dialog:
But if you really want to disable MDA, you can go Debug->Exceptions and uncheck Managed Debugging Assistance.
You can read more about pInvokeStackImbalance and MDA here and here.

Categories