I am currently working on migrating our company's x86 software to x64 and have encountered a snag. Our method from our unmanaged dll that returns a char* crashes with 0xc0000374 when the solution platform is switched to x64 and I'd like to know why.
C++ Native Code:
#include "stdafx.h"
#include <windows.h>
#include "windef.h"
#define VERSION "3.3.12"
extern "C"
{
__declspec(dllexport) char* WINAPI GetMyVersion()
{
return (char*)VERSION;
}
}
C# DllImport:
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace InteropTest.Adapter
{
[SuppressUnmanagedCodeSecurity]
public unsafe class MyAdapter
{
private const string DLLName = "VersionNumber";
private const string EntryPointName = "GetMyVersion";
[DllImport(DLLName, EntryPoint = EntryPointName, CharSet = CharSet.Ansi)]
internal static extern IntPtr GetMyVersionPtr();
[DllImport(DLLName, EntryPoint = EntryPointName, CharSet = CharSet.Ansi)]
internal static extern string GetMyVersionString();
}
}
GetMyVersionString() only works on x86 platforms and crashes on x64 ones. However, I am able to get the IntPtr and to use the Marshal class to convert it to a managed string on both x86 and x64 like such:
using System.Runtime.InteropServices;
using System.Windows;
using InteropTest.Adapter;
namespace InteropTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var ptr = MyAdapter.GetMyVersionPtr();
var convertedString = Marshal.PtrToStringAnsi(ptr);
// This crashes in x64 build
var directString = MyAdapter.GetMyVersionString();
VersionTextBlock.Text = convertedString;
}
}
}
I have also tried specifying the MarshalAs Attribute for the return value explicitly without success. It seems like the 64 bit char* is used as a 32 bit pointer if you do not explicitly use the Marshal class. Is this just a bug in .NET or did I do something wrong? From My point of view, converting with the Marshal class should be equivalent to using the MarshalAs Attribute.
EDIT:
Here is a sample project I've made to replicate the bug:
https://github.com/chenhuang444/dotNet40InteropCrash
EDIT 2:
The program and exits with code 0xc0000374 without native debugging and only states "InteropTest.exe has triggered a breakpoint" if I have native debugging on, with a stack trace of:
ntdll.dll!00007ffc0fdc4cfa() Unknown
ntdll.dll!00007ffc0fdcc806() Unknown
ntdll.dll!00007ffc0fdccad1() Unknown
ntdll.dll!00007ffc0fd69a55() Unknown
ntdll.dll!00007ffc0fd77347() Unknown
mscorlib.ni.dll!00007ffbd566759e() Unknown
[Managed to Native Transition]
> InteropTest.exe!InteropTest.MainWindow.OnLoaded(object sender, System.Windows.RoutedEventArgs e) Line 23 C#
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) Unknown
EDIT 3:
The unmanaged C++ code is a project in our solution and the x64/x86 configurations are set up, similar to the example project linked above.
Well, your version is not a "string", it's indeed a pointer to raw memory. The .NET framework will try to deallocate this pointer if you tell it it's a string.
So, you have two options:
use an IntPtr like you do, which is fine.
allocate a real string that .NET can understand, using the COM allocator, so in your case, your C++ code could be replace by this:
.
__declspec(dllexport) char* WINAPI GetMyVersion() {
char* p = (char*)CoTaskMemAlloc(7);
CopyMemory(p, VERSION, 7);
return p;
}
note: you can also use BSTRs.
Related
The project I am working on has a case where I have to read and display hardware info, for which they have a function written in a C++ DLL, and I am writing a C# stub to PInvoke it. I am fairly new to PInvoke, and hold beginner status in C# and C++ in general, as I mostly work in the Java space.
The issue here is that I am getting an exception, I feel like there is some marshaling issue going on here, which is causing this exception, but please correct me if I am wrong. Can you please help me spot the issue?
Also, I did try combining multiple MarshalAs options and different data types. In addition to the complication as it is, the function takes in a reference to a struct as an argument, and the struct itself has a nested struct within. Also, it returns a long as a flag I believe, when called with no arguments returns 0 with no exception, which is interesting.
===========================================================================================================
Part of sample hardware operation C++ dll header.
===========================================================================================================
#define APOINTER *
typedef unsigned char BYTETYPE;
typedef BYTETYPE CHARTYPE;
typedef unsigned long int ULONGTYPE;
typedef HDINFO APOINTER PTR_HDINFO;
typedef struct VERSION {
BYTETYPE major;
BYTETYPE minor;
}VERSION;
typedef VERSION APOINTER PTR_VERSION;
typedef struct HDINFO {
VERSION hardwareVersion;
CHARTYPE hardwareManufactureID[32]; /* blank padded */
ULONGTYPE hardwareflags; /* must be zero */
CHARTYPE hardwareDesc[32]; /* blank padded */
VERSION apiVersion;
}HDINFO;
typedef HDINFO APOINTER PTR_HDINFO;
extern "C" __declspec(dllexport) ULONGTYPE GetHardwareInfo(PTR_HDINFO pSoInfo);
===========================================================================================================
Sample C# code for PInvoke. Throws Exception
===========================================================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace ConsoleHardwareApp
{
[StructLayout(LayoutKind.Sequential, Size =1)]
struct VERSION
{
byte majorVersion;
byte minorVerison;
}
[StructLayout(LayoutKind.Sequential, Size =1)]
struct HDINFO
{
VERSION hardwareVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
string hardwareManufactureID;
int hardwareflags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
string hardwareDesc;
VERSION apiVersion;
}
class Program
{
[DllImport("sampleHardwareOp.dll")]
public static extern int GetHardwareInfo(ref IntPtr hdInfoPtr);
static void Main(string[] args)
{
HDINFO hdInfo = new HDINFO();
IntPtr hdInfoPtr = new IntPtr();
hdInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(HDINFO)));
Marshal.StructureToPtr<HDINFO>(hdInfo, hdInfoPtr, false);
int rv = 1;
rv = GetHardwareInfo(ref hdInfoPtr); //Exception thrown here
Console.WriteLine(rv);
Console.ReadKey();
}
}
}
The exception:
ConsoleHardwareApp.Program::GetHardwareInfo' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
Without concise details about which C++ compiler was used to make the DLL, what size its long data type is (may be 32bit or 64bit), what alignment settings are used, if any structure padding is present, etc, then a translation to C# is difficult. However, your C# code should probably look something more like the following instead:
namespace ConsoleHardwareApp
{
[StructLayout(LayoutKind.Sequential/*, Pack=4 or 8*/)]
struct VERSION
{
byte major;
byte minor;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi/*, Pack=4 or 8*/)]
struct HDINFO
{
VERSION hardwareVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
string hardwareManufactureID;
uint hardwareflags; // or ulong, depending on the C++ compiler used
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
string hardwareDesc;
VERSION apiVersion;
}
class Program
{
[DllImport("sampleHardwareOp.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern uint/*or: ulong*/ GetHardwareInfo(ref HDINFO pSoInfo);
static void Main(string[] args)
{
HDINFO hdInfo = new HDINFO();
uint rv = GetHardwareInfo(ref hdInfo);
Console.WriteLine(rv);
Console.ReadKey();
}
}
}
The CallingConvention in particular is very important. The default calling convention in C++ is usually __cdecl (but can be changed in compiler settings), however in C# the default CallingConvention in DllImport is CallingConvention.Winapi, which is __stdcall on Windows. A calling convention mismatch can easily corrupt the call stack, causing the error you are seeing.
I am learning C# from my C++/CLR background by rewriting a sample C++/CLR project in C#.
The project is a simple GUI (using Visual Studio/ Windows Forms) that performs calls to a DLL written in C (in fact, in NI LabWindows/CVI but this is just ANSI C with custom libraries). The DLL is not written by me and I cannot perform any changes to it because it is also used elsewhere.
The DLL contains functions to make an RFID device perform certain functions (like reading/writing RFID tag etc). In each of these functions, there is always a call to another function that performs writing to a log file. If the log file is not present, it is created with a certain header and then data is appended.
The problem is: the C++/CLR project works fine.
But, in the C# one, the functions work (the RFID tag is correctly written/read etc.) but there is no activity regarding the log file!
The declarations for DLL exports look like this (just one example, there are more of them, of course):
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[]);
The save_Logdatei function is called during execution of Magnetfeld_einschalten like this:
save_Logdatei(path_Logfile_RFID, "Magnetfeld_einschalten", "OK");
In the C++/CLR project, I declared the function like this:
#ifdef __cplusplus
extern "C" {
#endif
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
#ifdef __cplusplus
}
#endif
then a simple call to the function is working.
In the C# project, the declaration goes like:
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(string path_Logfile_RFID);
and, as I said, although the primary function is working (in this case, turning on the magnetic field of the RFID device), the logging is never done (so, the internal DLL call to save_Logdatei is not executing correctly).
The relevant code in the Form constructor is the following:
pathapp = Application.StartupPath;
pathlog = string.Format("{0}\\{1:yyyyMMdd}_RFID_Logdatei.dat", pathapp, DateTime.Now);
//The naming scheme for the log file.
//Normally, it's autogenerated when a `save_Logdatei' call is made.
Magnetfeld_einschalten(pathlog);
What am I missing? I have already tried using unsafe for the DLL method declaration - since there is a File pointer in save_Logdatei - but it didn't make any difference.
===================================EDIT==================================
Per David Heffernan's suggestion, i have tried to recreate the problem in an easy to test way. For this, i have created a very simple DLL ("test.dll") and I have stripped it completely from the custom CVI libaries, so it should be reproducible even without CVI. I have uploaded it here. In any case, the code of the DLL is:
#include <stdio.h>
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[]);
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300])
{
save_Logdatei(path_Logfile_RFID, "Opening Magnet Field", "Success");
return 0;
}
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[])
{
FILE *fp; /* File-Pointer */
char line[700]; /* Zeilenbuffer */
char path[700];
sprintf(path,"%s\\20160212_RFID_Logdatei.dat",path_Logdatei);
fp = fopen (path, "a");
sprintf(line, "Just testing");
sprintf(line,"%s %s",line, Funktion);
sprintf(line,"%s %s",line, Mitteilung);
fprintf(fp,"%s\n",line);
fclose(fp);
return 0;
}
The C# code is also stripped down and the only thing i have added to the standard Forms project, is Button 1 (and the generated button click as can be seen). The code is this:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace TestDLLCallCSharp
{
public partial class Form1 : Form
{
public int ret;
public string pathapp;
public string pathlog;
[DllImport("test", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(string path_Logfile_RFID);
public Form1()
{
pathapp = #"C:\ProgramData\test";
pathlog = string.Format("{0}\\20160212_RFID_Logdatei.dat", pathapp);
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
ret = Magnetfeld_einschalten(pathlog);
}
}
}
As can be seen, I have avoided using an automatic naming scheme for the log file (normally i use the date) and in both the dll and the C# code, the log file is "20160212_RFID_Logdatei.dat". I have also avoided using the app path as the directory where to put the log file and instead I have opted for a folder named test i created in ProgramData
Again, no file is created at all
This looks like a simple typo in your calling code. Instead of:
ret = Magnetfeld_einschalten(pathlog);
you mean to write:
ret = Magnetfeld_einschalten(pathapp);
In the C# code, these two strings have the following values:
pathapp == "C:\ProgramData\\test"
pathlog == "C:\ProgramData\\test\\20160212_RFID_Logdatei.dat"
When you pass pathlog to the unmanaged code it then does the following:
sprintf(path,"%s\\20160212_RFID_Logdatei.dat",path_Logdatei);
which sets path to be
path == "C:\\ProgramData\\test\\20160212_RFID_Logdatei.dat\\20160212_RFID_Logdatei.dat"
In other words you are appending the file name to the path twice instead of once.
An extensive overview for P/Invoke in C# is given in Platform Invoke Tutorial - MSDN Library.
The problematic bit is you need to pass a fixed char array rather than the standard char*. This is covered in Default Marshalling for Strings.
The gist is, you need to construct a char[300] from your C# string and pass that rather than the string.
For this case, two ways are specified:
pass a StringBuilder instead of a string initialized to the specified length and with your data (I omitted non-essential parameters):
[DllImport("MyDLL.dll", ExactSpelling = true)]
private static extern int Magnetfeld_einschalten(
[MarshalAs(UnmanagedType.LPStr)] StringBuilder path_Logfile_RFID);
<...>
StringBuilder sb = new StringBuilder(pathlog,300);
int result = Magnetfeld_einschalten(sb);
In this case, the buffer is modifiable.
define a struct with the required format and manually convert your string to it:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
struct Char300 {
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=300)]String s;
}
[DllImport("MyDLL.dll")]
private static extern int Magnetfeld_einschalten(Char300 path_Logfile_RFID);
<...>
int result = Magnetfeld_einschalten(new Char300{s=pathlog});
You can define an explicit or implicit cast routine to make this more straightforward.
According to UnmanagedType docs, UnmanagedType.ByValTStr is only valid in structures so it appears to be impossible to get the best of both worlds.
The String is in Unicode format, convert it to byte[]
Encoding ec = Encoding.GetEncoding(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage);
byte[] bpathlog = ec.GetBytes(pathlog);
and change parameter type to byte[]
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(byte[] path_Logfile_RFID);
For me it is working
JSh
I am attempting to access a Newlands scanner using Platform Invoke from a c# program.
It should be relatively straight forward.
I set up my P/Invoke and and call it.
[DllImport("NLcpfw.dll", EntryPoint = "cpfw_open", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr cpfw_open(string pwStrPort,
string pwStrParam,
int nMode);
public static void CallFromHere(){
IntPtr hDev = cpfw_open("udp", "", CPFW_OM_NORMAL);
//GCHandle handle = GCHandle.Alloc(HNLCPFW, GCHandleType.Pinned);
System.Diagnostics.Debug.Assert(hDev != null);
NewlandInterface.cpfw_close(hDev);
}
Some how it is not picking it up.
I get a BadImageFormatException was unhandled
Make sure it is a valid managed assembly
Make sure you have supplied a correct path for the assembly
The C++ header reads
__declspec(dllimport) HNLCPFW WINAPI cpfw_open(WCHAR *pwStrPort, WCHAR *pwStrParam, int nMode = CPFW_OM_NORMAL);
typedef struct
{
void* hDev;
int nMode;
PNLCPFW_PLUG_API DevAPI;
HINSTANCE hPlugDll;
void *exData;
}NLCPFW,*HNLCPFW;
I'm assuming it is ok just to take a IntPtr in my p\Invoke code.
Any ideas as to why this is happening would be much appreciated.
I have copied all of the dll's in with the exe. In this case these are
NLcpfw.dll, cpfw_udp.dll, cpfw_tcp.dll,cpfw_hidpos.dll etc
Thanks leppie I need to change the platform to X86
I am trying to learn P/Invoke, so I created a simple dll in C++
KingFucs.h:
namespace KingFuncs
{
class KingFuncs
{
public:
static __declspec(dllexport) int GiveMeNumber(int i);
};
}
KingFuns.cpp:
#include "KingFuncs.h"
#include <stdexcept>
using namespace std;
namespace KingFuncs
{
int KingFuncs::GiveMeNumber(int i)
{
return i;
}
}
So it does compile, then I copied this dll into my WPF's debug folder, with code:
[DllImport("KingFuncDll.dll", EntryPoint = "GiveMeNumber", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern int GiveMeNumber(
int i
);
And calling it in button click:
private void Button_Click(object sender, RoutedEventArgs e)
{
int num = GiveMeNumber(123);
}
But it gives me exception:
Unable to find an entry point named 'GiveMeNumber' in DLL
'KingFuncDll.dll'.
Really.... what have I done wrong... It obviously able to find the DLL, otherwise would be another exception. But my method name is exactly the same.... I can't think of other reason.
You need to use extern "C" when you export your function so that you suppress C++ name mangling. And you also should not try to p/invoke to members of a class. Use free functions instead:
extern "C" {
__declspec(dllexport) int GiveMeNumber(int i)
{
return i;
}
}
On the managed side your DllImport attribute is all wrong. Don't use SetLastError which is for Win32 APIs only. Don't bother setting CharSet if there are not text parameters. No need for ExactSpelling. And the calling convention is presumably Cdecl.
[DllImport("KingFuncDll.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int GiveMeNumber(int i);
The problem is that you are declaring the C++ "function" inside a C++ class and are telling P/Invoke to use StdCall.
Try to declare a C++ function outside a class and and export it like you did. Then your code should work.
If you really must have a C++ function inside a class, take a look at CallingConvention.ThisCall. But then you are responsible for creating your unmanaged class instance and pass it as the first parameter of your P/Invoke call
The entry point name of a dll file is given in .exp file which is found in debug folder where other source files are present. If dumpbin doesn't work you can try this.
What is the best practice to share memory of a struct from a C# program to a C++ win32 DLL?
I've used structs in managed shared memory using Boost between two C++ programs and it worked great. I'm lost on the best way to accomplish this between where the struct gets populated in the C# program and the C++ DLL that is an SNMP subagent.
Here's the C++ DLL:
//==================== Code Excerpt from the main cpp file ======================
#include "stdafx.h"
//================= Here we are setting up the shared memory area =====================
#pragma data_seg (".SHAREDMEMORY")
struct sharedData {
int sharedA;
int sharedB;
};
static sharedData A;
#pragma data_seg()
#pragma comment(linker,"/SECTION:.SHAREDMEMORY,RWS")
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
return TRUE;
}
//=============================================================================================
//====================== Here we are writing wrappers to the shared memory area ===========================
//=You must declare it as an Extern "C" to prevent name mangling. This is absolutely necessary in order to import it into c# =
//=============================================================================================
extern "C" __declspec(dllexport) sharedData __stdcall getMyData()
{
A.sharedA = 1237;
A.sharedB = 31337;
//return gshared_nTest;
return A;
}
extern "C" __declspec(dllexport) void __stdcall setMyData( sharedData buff )
{
A = buff;
}
Here's the calling C# function:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace sharedMemTestCS
{
public partial class frmSharedMemTestCS : Form
{
struct sharedData {
int sharedA;
int sharedB;
};
static sharedData A;
//============== here we are importing the methods from the win32 dll into the c# console application =================
[DllImport(#"C:\Documents and Settings\My Documents\Visual Studio 2010\Projects\sharedMemTestCPP\Debug\sharedMemTestCPP.dll")]
public static extern sharedData getMyData();
[DllImport(#"C:\Documents and Settings\My Documents\Visual Studio 2010\Projects\sharedMemTestCPP\Debug\sharedMemTestCPP.dll")]
public static extern void setMyData(int data);
public frmSharedMemTestCS()
{
InitializeComponent();
//============== here i am incrementing the value =================
//== i use a message box so that i can have multiple console applications running at once and it will pause at the messagebox (if i don't click ok)
//== i do this so i can see the values changing in the shared memory.
//MessageBox.Show( getMyData().ToString() );
getMyData();
//txtBoxA.Text = (getMyData().ToString() );
}
private void btnAdd_Click(object sender, EventArgs e)
{
//setMyData( getMyData() + 100 );
//txtBoxA.Text = (getMyData().ToString() );
}
}
}
The error message I get is:
Error 1 Inconsistent accessibility: return type
'sharedMemTestCS.frmSharedMemTestCS.sharedData' is less accessible
than method
'sharedMemTestCS.frmSharedMemTestCS.getMyData()' c:\documents and
settings\mconrad\my documents\visual studio
2010\Projects\sharedMemTestCS\sharedMemTestCS\Form1.cs 23 37 sharedMemTestCS
The best practice for sharing memory would be to use the MemoryMappedFile class in C# and CreateFileMapping/MapViewOfFile in C++.
First thing, you cannot straightway use Boost for data sharing. You need to have some well-defined data-structures that you share between the managed and unmanaged worlds.
You may start here
Well your actual problem is your p/invoke expression is public but your struct is private, which is what the error is telling you. Making your p/invoke expression private or your struct public will resolve the immediate issue.
As for the actual data sharing, I've never tried doing it quite like that so I can't tell you if it will or won't work. All the pieces I've worked with are marshalled back and forth. Looking at your example code, it's quite possible it could work. You'll probably want to either copy the data to a new struct for c# or pin your struct that you get back so the GC won't move it around in memory.