I am using an unmanaged dll that is written in C/C++ from a C# application. I'm interested in using the following function from the dll:
static void StorePath(const std::string& path, wchar_t *out_path,
int *out_path_length){
wcslcpy(out_path, c_str_w(path), *out_path_length);
*out_path_length = path.size();
}
int WINAPI BrowseForDirectory(
int allow_portable, int allow_online,
wchar_t *t_directory, int *e_directory_length,
wchar_t *m_directory, int *m_directory_length){
.
.
. //initializing new forms and checking product keys
StorePath(form->SelectedEDirectory().TopDir(), e_directory,
e_directory_length);
StorePath(form->SelectedMDirectory(), m_directory,
m_directory_length);
}
Header file:
#if defined(_WIN32) && !BUILD_WITHOUT_DLLS &&!defined(ECLIPSE_CBUILDER_WORKAROUNDS)
# if BUILDING_EXPORT_LIBRARY
# define EXPORT_DLL __declspec(dllexport)
# else
# define EXPORT_DLL __declspec(dllimport)
# endif
#else
# define EXPORT_DLL
#endif
extern "C" {
int WINAPI BrowseForDirectory(
int allow_portable, int allow_online,
wchar_t *t_directory, int *e_directory_length,
wchar_t *m_directory, int *m_directory_length)
}
Then, I am trying to invoke this function in my own managed, C# class library by doing the following:
[DllImport("MyDLL.dll", CharSet = CharSet.Ansi)]
public static extern int BrowseForDirectory(Int32 allowOnline,
Int32 allowPortable,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder eDirectory,
ref Int32 eDirLength,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder mDirectory,
ref Int32 mDirLength);
Finally, I'm trying to use it in a C# application by calling it like:
var eDir = new StringBuilder(260);
var mDir = new StringBuilder(260);
var eDirLength = eDir.Length;
var mDirLength = mDir.Length;
try
{
var result = Viewer.BrowseForDirectory(1, 1, eDir,
ref eDirLength, mDir, ref mDirLength);
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
}
However, I was getting a heap corruption, but now my application is exiting because of a STATUS_STACK_BUFFER_OVERRUN--something about an embedded breakpoint. Changing the C++ code is not an option. I have the proper reference and assemblies.
What am I doing wrong?
The problem that I can see is that your character sets do not match. The unmanaged code returns the text as UTF-16, but your p/invoke specifies ANSI encoded text. Change the p/invoke to:
[DllImport("MyDLL.dll", CharSet = CharSet.Unicode)]
public static extern int BrowseForDirectory(
int allowOnline,
int allowPortable,
StringBuilder eDirectory,
ref int eDirLength,
StringBuilder mDirectory,
ref int mDirLength
);
I'm assuming that c_str_w() takes an 8 bit encoded string and returns a pointer to null-terminated array of wchar_t.
Related
I am attempting to use a C function through C# Interop and I am receiving an access violation on one function. I have tried a number of things and I can't seem to solve this.
Here is the C code that needs to be changed into c# code:
typedef struct
{
char SerNo[64];
unsigned char hwVer;
HANDLE device; // Set by the API on return from SelectDevice()
} DeviceT;
This struct is used by the following function:
error = GetDevices(DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs)
There is one other function in the C code:
error = SelectDevice(DeviceT *device)
So I began by defining DeviceT. I tried a few ways, but settled on this since it is simple:
[StructLayout(LayoutKind.Sequential)]
public struct DeviceT
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public char[] SerNo;
public byte hwVer;
public IntPtr device;
}
The GetDevices function was set to this:
[DllImport("file.dll", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern ErrT GetDevices([In, Out] DeviceT[] devices, uint* numDevs, uint maxDev);
The SelectDevices function was set to this:
[DllImport("file.dll", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern ErrT SelectDevice([In, Out] DeviceT devices);
The code goes like this:
uint numDevs = 6;
uint maxDev = 6;
uint chosenIdx = 0;
DeviceT[] devices = new DeviceT[6];
err = GetDevices(devices, &NumberOfDevices, maxDev))
At this point everything is correct. The devices array has the correct information in it.
I now continue with (I just hard code select the first device)
chosenIdx = 0;
var chosenDevice = devices[chosenIdx];
err = SelectDevice(chosenDevice);
This last function returns a System.Access Violation
I tried a whole bunch of things but all end up with the same result. I suspect it has something to do with the HANDLE but I am not sure.
Thanks for any help.
SelectDevice takes a DeviceT *, but your P/Invoke signature takes a DeviceT. That is, you're passing in DeviceT by value rather than passing a pointer.
Try:
[DllImport("file.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern ErrT SelectDevice([In, Out] ref DeviceT devices);
err = SelectDevice(ref chosenDevice);
I have a function in socket.dll library as below:
__declspec(dllexport) string GetPib(const wchar_t* pib_key_chars)
{
wstring ws(pib_key_chars);
std::string pib_key(ws.begin(), ws.end());
cout << "In GetPib" << " and pib_key in string=" << pib_key << endl;
Pib pb;
return std::to_string(pb.Get(phyFskTxPacket, 0));
}
When i use "dumpbin /exports socket.dll" to check the GetPib function of socket.dll signature it returns
1 0 00011370 ?GetPib##YA?AV?$basic_string#DU?$char_traits#D#std##V?
$allocator#D#2##std##V12##Z = #ILT+875(?GetPib##YA?AV?$basic_string#DU?
$char_traits#D#std##V?$allocator#D#2##std##V12##Z)
I am using the same signature in the C# project(WindowsFormsApp1) to call/invoke GetPib function.
Below is the C# source code to invoke GetPib function:
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
[DllImport("socket.dll", EntryPoint = "?GetPib##YA?AV?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std##PB_W#Z", CallingConvention = CallingConvention.Cdecl)]
public static extern string GetPib([MarshalAs(UnmanagedType.LPStr)] string pib_key);
public Form1()
{
InitializeComponent();
}
private void GetPib_button_Click(object sender, EventArgs e)
{
String str = pib_id_tbox.Text;
pib_uvalue_tbox.Text = GetPib(str);
}
}
When I invoke GetPib function like GetPib(0x1004xxx4), it invokes the socket.dll's GetPib function but the value is different with special characters
str=0x10040004 tr=268697604-----> in C# file
In GetPib and pib_key in string=h䤰„------> in C++ .dll file
How to fix this issue.
First of all, wrap the unmanaged function declaration in extern "C" to eliminate the mangling and rewrite the unmanaged function to something more sensible for interop with managed code.
extern "C" {
__declspec(dllexport) int GetPib(const char* pib_key_chars, char *result, int len) {
cout << "In GetPib" << " and pib_key in string=" << pib_key_chars << endl;
Pib pb;
string packet = std:to_string(pb.Get(phyFskTxPacket, 0);
int n = (int)packet.length;
if (n < len) {
packet.copy(result, n, 0);
result[n] = '\0';
}
return n + 1;
}
}
In this rewrite, we are passing the managed string, an allocated buffer to hold the result, and the length of that buffer. This version handles char* strings. If you need to use wchar_t* wide strings, it should be trivial to convert it. The main thing to note is that we are not using any C++ types as parameters to the function, since the interop marshaller has no way of knowing how to deal with those types. We are only using language primitives.
The managed P/Invoke signature would look like this:
[DllImport("socket.dll", EntryPoint="GetPib")]
public static extern int GetPib(([MarshalAs(UnmanagedType.LPStr)] string keyChars, IntPtr buffer, int len);
To call it:
private void GetPib_button_Click(object sender, EventArgs e)
{
int needed = GetPib(pib_id_tbox.Text, IntPtr.Zero, 0);
IntPtr p = Marshal.AllocHGlobal(needed);
GetPib(pib_id_tbox.Text, p, needed);
pib_uvalue_tbox.Text = Marshal.PtrToStringAuto(p);
Marshal.FreeHGlobal(p);
}
Here we are allocating unmanaged memory so that the GC won't move it around while we are interoperating with unmanaged code. The unmanaged function will fill the buffer with the resulting char*, which we convert back into a managed string with PtrToStringAuto(). Then we free the unmanaged memory.
I followed this link
https://answers.unity.com/questions/142150/passing-strings-to-and-from-a-c-dll.html
I modified GetPib api in C++
__declspec(dllexport) string GetPib(const wchar_t* pib_key_chars)
to
extern BSTR __declspec(dllexport) GetPib(BSTR pib_key_chars)
Modified C# file from
[DllImport("socket.dll", EntryPoint = "?GetPib##YA?AV?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std##PB_W#Z", CallingConvention = CallingConvention.Cdecl)]
public static extern string GetPib([MarshalAs(UnmanagedType.LPStr)] string pib_key);
to
[DllImport("socket.dll", EntryPoint = "?GetPib##YAPA_WPA_W#Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GetPib(string str);
and it solved my problem!!!!!!
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).
I use the following DllImport:
[DllImport(#"someDLL.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern UINT64 someFunc(int arga, int argb, int argc);
I'm calling the function as follows:
someFunc(0,0,1);
In h file i declare the function:
extern "C" __declspec(dllexport) UINT64 someFunc(int arga, int argb, int argc);
cpp:
UINT64 someFunc(int arga, int argb, int argc)
{
...
}
In the C++ code I receive weird values (such as 1218628, 20140292, 1219020).
Any idea why?
You didn't show the C++ code so I don't see the problem in your code. So, I tried recreating it myself. I created a C# WPF project which calls into a DLL.
C#:
[DllImport(#"c:\users\owner\documents\visual studio 2010\Projects\MyDll\Release\MyDll.dll",
CallingConvention = CallingConvention.Cdecl)]
private static extern UInt64 someFunc(int arga, int argb, int argc);
private void DoIt_Click(object sender, RoutedEventArgs e)
{
UInt64 val = someFunc(0, 0, 1);
ResultLabel.Content = val.ToString();
}
C++ DLL:
extern "C" __declspec(dllexport) unsigned __int64 someFunc(int arga, int argb, int argc)
{
CString s;
s.Format(L"%d\t%d\t%d", arga, argb, argc);
AfxMessageBox(s);
return arga + argb + argc;
}
The message box from C++ shows 0 0 1 as expected and the C# code gets 1 returned as expected.
Use __stdcall instead of __cdecl if you can, in __stdcall is the C DLL that performs stack cleanup and is the standard way windows C dlls are used.
It is also a good behaviour to specify the calling convention in your C code, both for readability and because C++ project settings can specify what calling convention use by default.
Try to set it __cdecl explicitly, maybe c++ compiler is compiling it using __stdcall.
extern "C" __declspec(dllexport) UINT64 __cdecl someFunc(int arga, int argb, int argc);
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?