When trying to call a C++ DLL in C#, I encounter an exception (System.AccessViolationException) and I have no idea why. The C#, as well as the C++ Project are compiled in x64. In the Dll.cpp the dll-methods are exported. In this methods a class called OpcClient is used, which is located in the same project. My IDE is Visual Studio Express 2012.
Can anybody help?
C# Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace OCTTester
{
class Program
{
static void Main(string[] args)
{
string[] array = new string[2];
array[0] = "Tag1";
array[1] = "Tag2";
Connect("IBHSoftec.IBHOPC.DA", array, 2, 0, 100);
Add("Tag1", 10);
Stop();
}
[DllImport(#"OPCClient.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void Connect(string hostname, string[] tagnames, int amount_tags, int buffertype, int refreshrate);
[DllImport(#"OPCClient.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void Add(string tagname, double value);
[DllImport(#"OPCClient.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void Stop();
}
}
C++ Code (Dll.cpp):
#include "stdafx.h"
#include <exception>
#include "OpcClient.h"
using namespace std;
extern "C" { // we need to export the C interface
std::thread *output_thread = NULL;
_declspec(dllexport) int __cdecl Connect(const char *hostname, const char **tagnames, int amount_tags, int buffertype, int refreshrate)
{
OpcClient* opc_client = OpcClient::getInstance();
vector<string> vec_tagnames;
for (int i = 0; i < amount_tags; i++)
{
vec_tagnames.push_back(tagnames[i]);
}
try
{
opc_client->Connect(hostname, vec_tagnames, refreshrate, buffertype);
}
catch(exception &e)
{
std::cout << e.what() << "\n";
return -1;
}
output_thread = new thread(&OpcClient::Start, opc_client);
return 0;
}
_declspec(dllexport) int __cdecl Add(const char *tagname, double value)
{
OpcClient* opc_client = OpcClient::getInstance();
opc_client->AddValueToBuffer(tagname, value);
return 1;
}
_declspec(dllexport) int __cdecl Stop(void)
{
OpcClient* opc_client = OpcClient::getInstance();
opc_client->stop_ = true;
output_thread->join();
return 1;
}
}
Related
I have a C++ DLL that I'm trying to use in a C# application. The C++ DLL returns an object containing several vector types. C# throws a Method's type signature is not PInvoke compatible.
[StructLayout( LayoutKind.Sequential )]
public struct InputDLL
{
public int a;
public int b;
public int c;
public int d;
public int e;
};
[StructLayout( LayoutKind.Sequential )]
public struct OutputDLL
{
public List<int> vectorout;
};
public class TestVectorDLL
{
[DllImport( "TestVectorDLL.dll",
EntryPoint = "?RunDLL##YA?AUOutputDLL##UInputDLL###Z",
CallingConvention = CallingConvention.Cdecl )]
public static extern OutputDLL RunDLL( InputDLL input );
}
There was a DLL written for the sole purpose of me testing a return type containing a vector. The DLL takes the 5 integer values as input and returns those 5 integers in a vector data type. My code to use the DLL is:
InputDLL input = new InputDLL()
{
a = 1,
b = 2,
c = 3,
d = 4,
e = 5
};
OutputDLL output = TestVectorDLL.RunDLL(input);
The above line throws a Method's type signature is not PInvoke compatible.
Can someone point me on how to read the C++ return correctly?
Here is the testing C++ DLL's .h include:
#pragma once
#include <string>
#include <vector>
#include <array>
using namespace std;
#define EPS_API __declspec(dllexport)
struct InputDLL
{
int a;
int b;
int c;
int d;
int e;
};
struct OutputDLL
{
vector<int> vectorout;
};
EPS_API OutputDLL RunDLL(InputDLL Input);
When passing arguments between managed and native methods, you should stick to the available marshalling types or implement your own custom marshaller. AFIK. there is no standard marshaller for std:vector. You have two options: 1. A more humble implementation using available marshalling types (see the code below). 2. Implement ICustomMarshaller interface for std:vector. You will find a description of this interface here:
ICustomMarshaller interface
// A more modest marshalling example:
// C# code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
[StructLayout(LayoutKind.Sequential)]
public struct input_struct
{
public int a;
public int b;
public int c;
}
[StructLayout(LayoutKind.Sequential)]
public struct output_struct
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public int[] o;
}
public class NativeVector
{
[DllImport("NativeVector.dll")]
public static extern int RunDll(input_struct i, ref output_struct o);
}
class Program
{
static output_struct output = new output_struct();
static void Main(string[] args)
{
input_struct input;
input.a = 1;
input.b = 2;
input.c = 3;
output.o = new int[3];
NativeVector.RunDll(input, ref output);
}
}
}
// C++ code
#include "stdafx.h"
#include <vector>
struct input
{
int a;
int b;
int c;
};
struct output
{
int v[3];
};
extern "C"
{
__declspec(dllexport) int _stdcall RunDll(struct input i, struct output& o)
{
o.v[0] = i.a;
o.v[1] = i.b;
o.v[2] = i.c;
return 0;
}
}
I'm sorry if this sounds too specific, but I need to get this done exactly this way and I'm stuck at it for days. In my real scenario, I have a MyStruct **ppObject which address must be passed to a third-party dll so that it will point to an array of structs. Afterwards, I must p/Invoke this same pointer to array, but I'm having problems with the contents of the struct. Here's a MCVE:
Unmanaged.h:
#ifndef _NATIVELIB_H_
#define _NATIVELIB_H_
#ifndef MCVE
#define MCVE
#endif
struct PlcVarValue
{
unsigned char byData[8];
};
#ifdef __cplusplus
extern "C" {
#endif
MCVE __declspec(dllexport) void FillArray(void);
MCVE __declspec(dllexport) void Clear(void);
MCVE __declspec(dllexport) PlcVarValue** GetValues(void);
#ifdef __cplusplus
}
#endif
#endif // _NATIVELIB_H_
Unmanaged.cpp
#include "stdafx.h"
#include "Unmanaged.h"
#include <iostream>
PlcVarValue** values;
MCVE __declspec(dllexport) void FillArray(void) {
values = (PlcVarValue**)malloc(sizeof(PlcVarValue*) * 5);
for (int i = 0; i < 5; i++)
{
values[i] = new PlcVarValue();
*values[i]->byData = i;
}
}
MCVE __declspec(dllexport) void Clear(void) {
delete *values;
free(values);
}
MCVE __declspec(dllexport) PlcVarValue** GetValues(void) {
return values;
}
PlcVarValue.cs
using System.Runtime.InteropServices;
namespace Managed
{
[StructLayout(LayoutKind.Sequential)]
public class PlcVarValue
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Data;
}
}
ManagedClass.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Managed
{
public class ManagedClass
{
public ManagedClass()
{
FillArray();
}
[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void FillArray();
[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetValues();
[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void Clear();
public List<PlcVarValue> GetList()
{
int size = 5;
var list = new List<PlcVarValue>();
IntPtr ptr = GetValues();
var gch = GCHandle.Alloc(ptr, GCHandleType.Pinned);
try
{
int memSize = Marshal.SizeOf(typeof(PlcVarValue));
for (int i = 0; i < size; i++)
{
//I know this is wrong, it would work for a PlcVarValue* but I need it to work with a PlcVarValue**
list.Add((PlcVarValue)Marshal.PtrToStructure(new IntPtr(ptr.ToInt32() + memSize * i), typeof(PlcVarValue)));
}
}
finally
{
gch.Free();
}
return list;
}
public void FreeMemory()
{
Clear();
}
}
}
Program.cs
using Managed;
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var managed = new ManagedClass();
var list = managed.GetList();
foreach(var value in list)
Console.WriteLine(BitConverter.ToInt32(value.Data, 0));
managed.FreeMemory();
Console.ReadKey();
}
}
}
I hope that code is self-explanatory, I'll provide more information if needed. My problem is that the byte array (which I'm trying to marshal from the aforementioned unsigned char array) in the Program class prints different random data every time I run the program but when I check the C++ side with the debugger (before marshalling takes place), everything is fine. Like I mentioned in a comment in the code above, I believe the problem lies in this line in the ManagedClass class:
list.Add((PlcVarValue)Marshal.PtrToStructure(new IntPtr(ptr.ToInt32() + memSize * i), typeof(PlcVarValue)));
I know for a fact that this works fine with MyStruct *pObject where *pObject is an array of the same struct, but in this case I need a pointer to a pointer since what the third-party dll requires is actually a MyStruct ***pppObject (beats me, but that's what I have to deal with). I have tried to copy data from ppObject to a single pointer but, even though it worked, I was not satisfied with the result as it had some undesired side-effects in the real application. Also, should my usage of GCHandle be wrong, I'd gladly accept advice on how to fix it, but that's not the main focus of this question.
The C# side:
public List<PlcVarValue> GetList()
{
int size = 5;
var list = new List<PlcVarValue>(size);
IntPtr ptr = GetValues();
for (int i = 0; i < size; i++)
{
IntPtr ptrPlc = Marshal.ReadIntPtr(ptr, i * IntPtr.Size);
var plc = (PlcVarValue)Marshal.PtrToStructure(ptrPlc, typeof(PlcVarValue));
list.Add(plc);
}
return list;
}
and the C++ side (the only error was in the Clear()):
__declspec(dllexport) void Clear(void)
{
for (int i = 0; i < 5; i++)
{
delete values[i];
}
free(values);
}
BUT you shouldn't mix malloc and new!
If you need an explanation about what I'm doing, remember that you are returning a pointer to an array of pointers!
I have a dll file which is written by C++ (the name of file is "DllForTest.dll"), this is its code:
#include "stdafx.h"
#include <vector>
using namespace std;
double *ret;
double* _stdcall f(int* n)
{
vector<double> vret;
int i=0;
do
{
vret.push_back(i);
i++;
} while (the condition to stop this loop);
*n=i;
ret = new double[*n];
for (i=0;i<*n;i++)
ret[i]=vret[i];
return ret;
}
This is C# code to call f function from the dll file above to get return value:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowForm
{
public partial class Form1 : Form
{
[DllImport("DllForTest.dll")]
public static extern double[] f(ref int n);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
int n=0;
double[] x;
x = f(ref n);
MessageBox.Show("x[0]= " + x[0]);
}
}
}
When I run, it generate an error:
Cannot marshal 'return value': Invalid managed/unmanaged type combination.
How to fix it to gain wanted result? Thanks.
Try specifying return value as IntPtr instead of double[] and then use Marshal.Copy to copy data from this IntPtr to your double[] array:
[DllImport("DllForTest.dll")]
static extern IntPtr f(ref int n);
private void button1_Click(object sender, EventArgs e)
{
int n=0;
IntPtr intPtr = f(ref n);
double[] x = new double[n];
Marshal.Copy(intPtr, x, 0, n);
MessageBox.Show("x[0]= " + x[0]);
}
I work on my program, which I need for my bachelor work(C#/C++ interoperability) and I have problem with missing entry point in my code... I try create simply number generator, which will be genarated number in C++ class calling from C# ... At first I don't know how I pass a class, but then I found way how to do it on this page... Please help me fix it...
I added my code:
[C++]
#include <iostream>
#include <cstdlib>
#include <time.h>
using namespace std;
__declspec(dllexport) class Generator
{
private:
int zaciatok;
int koniec;
int pocetprvkov;
int *pole;
public:
Generator(){}
void Vytvor (int zaciatok, int koniec, int pocetprvkov)
{
srand((unsigned)time(0));
pole= new int [pocetprvkov];
}
void Napln()
{
for(int a=0; a<pocetprvkov; a++)
{
pole[a] = rand() % (koniec - zaciatok +1) + zaciatok;
}
}
void Vypis()
{
for(int a=0; a<pocetprvkov; a++)
cout << pole[a] << endl;
}
~Generator()
{
delete[] pole;
pole= 0;
}
};
extern "C"
{
__declspec(dllexport) Generator* Vytvor_Triedu() { return new Generator(); }
__declspec(dllexport) void Vytvor(Generator* prva) {prva->Vytvor(5,25,4); }
__declspec(dllexport) void Napln(Generator* prva) {prva->Napln(); }
__declspec(dllexport) void Vypis(Generator* prva) {prva->Vypis(); }
__declspec(dllexport) void Vymaz(Generator* prva) { delete prva; }
}
[C#]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace GeneratorCsharp
{
class Program
{
[DllImport("DllTriedaGenerator.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Vytvor_Triedu();
[DllImport("DllTriedaGenerator.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Vytvor(IntPtr value);
[DllImport("DllTriedaGenerator.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Napln(IntPtr value);
[DllImport("DllTriedaGenerator.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Vypis(IntPtr value);
[DllImport("DllTriedaGenerator.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Vymaz(IntPtr value);
static void Main(string[] args)
{
IntPtr trieda = Vytvor_Triedu();
Vytvor(trieda);
Napln(trieda);
Vypis(trieda);
Vymaz(trieda);
}
};
}
Thanks a lot!
Well, you should use dumpbin, find the mangled name of your function then add EntryPoint="yourMangledName" in your DllImport attribute.
I have the following C Header/Code Example:
Header file
struct category_info {
int id;
const char *name;
const char *description;
};
DLLEXPORT
void* xyz_categories_info(struct category_info **info, size_t *info_count);
Example C Snippet
struct category_info *catinfo;
size_t catcount;
size_t i;
int max_name_len = 0;
void *catmem = xyz_categories_info(&catinfo, &catcount)
Which I would like to convert to c#...
My First GUESS (and its a guess) is:
[StructLayout(LayoutKind.Sequential)]
public struct category_info
{
int id;
[MarshalAs(UnmanagedType.LPStr)]
StringBuilder name;
[MarshalAs(UnmanagedType.LPStr)]
StringBuilder description;
};
[DllImport("mydll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr xyz_categories_info([Out]category_info cat, [Out]int catSize);
But it just doesn't look right..
Any suggestions.. Once the above is declared correctly in C#.. How should it be accessed in C#
category_info catinfo;
catmem = xyz_categories_info(out catinfo, out catcount);
??????
Any help greatly appreciated.
Thanks
================================================================================
Update 2
The memory allocated in xyz_categories_info is freed using this C call:
void xyz_categories_info_free(void *p);
Below is an example of it being used in C.... Hope this explains it a bit more..
category_buffer = xyz_categories_info(&category_info, &category_count);
if( !category_buffer )
{
// Failed Log a message and exit.
exit(1);
}
for(j=0; j<category_count; j++)
{
if( category_info[j].id == 0 )
continue;
printf("id: %d name: '%s' description: '%s'\n",
category_info[j].id,
category_info[j].name,
category_info[j].description
);
}
xyz_categories_info_free(category_buffer);
This code is compiled, but not tested. If you know C, you will understand what happens here, this is just the same C code translated to C#.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
public struct category_info
{
public int id;
public IntPtr name;
public IntPtr description;
};
class Program
{
[DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr xyz_categories_info(ref IntPtr cat, ref int catSize);
[DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void xyz_categories_info_free(IntPtr cat);
static void Main(string[] args)
{
IntPtr categories = IntPtr.Zero;
IntPtr category_buffer = IntPtr.Zero;
int category_count = 0;
category_info info = new category_info();
IntPtr current;
try
{
category_buffer = xyz_categories_info(ref categories, ref category_count);
if (category_buffer == IntPtr.Zero)
{
return;
}
if (category_count == 0)
{
return;
}
for (int j = 0; j < category_count; j++)
{
if (IntPtr.Size == 4)
{
current = new IntPtr(categories.ToInt32() + j * Marshal.SizeOf(info));
}
else
{
current = new IntPtr(categories.ToInt64() + j * Marshal.SizeOf(info));
}
info = (category_info)Marshal.PtrToStructure(current, typeof(category_info));
if (info.id == 0)
{
continue;
}
Console.WriteLine(info.id);
Console.WriteLine(Marshal.PtrToStringAnsi(info.name));
Console.WriteLine(Marshal.PtrToStringAnsi(info.description));
}
}
finally
{
if (category_buffer != IntPtr.Zero)
{
xyz_categories_info_free(category_buffer);
}
}
}
}
}
This is correct for importing DLL's functions
[DllImport("mydll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr xyz_categories_info([Out]category_info cat, [Out]int catSize);
But not sure about the OUT
Your C code of that
struct category_info {
int id;
const char *name;
const char *description;
};
I bilive should be C# Class
public class category_info
{
public const string name {get; set};
public const string description {get; set};
public int id {get; set;}
public category_info(int id, const string name, const string description){
this.name = name;
this.description = description;
this.id = id;
}
}
As to using it and using the code i'm not sure what your trying todo
size_t catcount;
size_t i;
int max_name_len = 0;
void *catmem = xyz_categories_info(&catinfo, &catcount)
This in C# I'm not sure about as size_t would have to be an class in C# but then that class has to match exactly what the DLL class is or there will be a type mismatch this is the problem with loading cross lang DLLS
What is that DLL supposed to be doing? maybe we can help