Use C DLL in C# project - c#

I made a project for my ATM card reader in a C++ project and it works fine. When I try to use the same header file and DLL in a C# project, I keep on getting this error:
System.ExecutionEngineException: 'Exception of type 'System.ExecutionEngineException' was thrown.'
C++ Code
HANDLE port = CommOpenWithBaut(1, 9600);
if (port == 0)
{
cerr << "Cannot open device";
return -1;
}
unsigned char PewSt = 0;
unsigned char CardType = 0;
unsigned char CardSt = 0;
unsigned char PowerSt = 0;
int rc = CarderCheckSt(port, PewSt, CardType, CardSt, PowerSt);
//cout << "This is the card Status: " << CarderCheckSt << endl;
int bufferSize = 128;
BYTE buffer[128];
int bytesRead = MagRead(port, buffer, bufferSize);
string text(buffer, buffer + bufferSize);
cerr << "Card Details : " <<text;
CommClose(port);
Port is the handle for configuring the port function CommOpenWithBaut(). CarderCheckSt() checks the status of the ATM card reader. MagRead() has the buffer data read from the card and text() converts that buffer to ASCII code. CommClose() closes the port after completion.
C# Code
[DllImport("F:\\ajm\\Components\\ATM\\New folder (2)\\ATMDev\\ATMCsharp\\inc\\ModuleV188.dll", EntryPoint = "CommOpenWithBaut")]
public static extern Handle CommOpenWithBaut(uint nPort, uint _data);
[DllImport("F:\\ajm\\Components\\ATM\\New folder (2)\\ATMDev\\ATMCsharp\\inc\\ModuleV188.dll", EntryPoint = "CommClose")]
public static extern int CommClose(Handle hComHandle);
[DllImport("F:\\ajm\\Components\\ATM\\New folder (2)\\ATMDev\\ATMCsharp\\inc\\ModuleV188.dll", EntryPoint = "MagRead")]
public static extern int MagRead(Handle hComHandle, Byte[] MagData, int lenth);
[DllImport("F:\\ajm\\Components\\ATM\\New folder (2)\\ATMDev\\ATMCsharp\\inc\\ModuleV188.dll", EntryPoint = "MagRead")]
public static extern int CarderCheckSt(Handle hComHandle, char PewSt, char CardType, char CardSt, char PowerSt);
public Form1()
{
InitializeComponent();
}
public void button1_Click(object sender, EventArgs e)
{
Handle port = CommOpenWithBaut(1, 9600);
Byte[] MagData = {128};
int lenth = 128;
MagRead(port, MagData, lenth);
char PewSt = '0';
char CardType = '0';
char CardSt = '0';
char PowerSt = '0';
CarderCheckSt(port, PewSt, CardType, CardSt, PowerSt);
MessageBox.Show($"CarderCheckSt");
CommClose(port);
}
I used DllImport to import the same 4 functions from the DLL I used in the previous project. I added a button when clicked, it is suppose to do the same.
This is the Header file
#include <iostream>
#include <windows.h>
#ifdef ModuleV188_EXPORTS
#define ModuleV188_API extern "C" __declspec(dllexport)
#else
#define ModuleV188_API extern "C" __declspec(dllimport)
#endif
extern "C" ModuleV188_API HANDLE CommOpenWithBaut(UINT nPort, UINT _data);
extern "C" ModuleV188_API int CommClose(HANDLE hComHandle);
extern "C" ModuleV188_API int CarderCheckSt(HANDLE hComHandle, unsigned char& PewSt, unsigned char& CardType, unsigned char& CardSt, unsigned char& PowerSt);
extern "C" ModuleV188_API int MagRead(HANDLE hComHandle, BYTE* MagData, int& lenth);
Also, when the program communicates with the ATM card reader, it writes to a log. The bytes received in the log for both programs.
for the C++ SUCCESSFUL READ :
18:58:37 COM1´ٍ؟ھ³ة¹¦
18:58:38 ========> 02000231440376
18:58:39 <======== 0200043144303103
18:58:39 <======== 020004314430310371
18:58:39 ========> 0200023B35030D
18:58:39 <======== 0200773B35594234
in the C# log:
10:16:37 COM1´ٍ؟ھ³ة¹¦
FIX
[DllImport("F:\\ajm\\Components\\ATM\\New folder (2)\\ATMDev\\ATMCsharp\\inc\\ModuleV188.dll", EntryPoint = "CommOpenWithBaut")]
public static extern IntPtr CommOpenWithBaut(uint nPort, uint _data);
[DllImport("F:\\ajm\\Components\\ATM\\New folder (2)\\ATMDev\\ATMCsharp\\inc\\ModuleV188.dll", EntryPoint = "CommClose")]
public static extern int CommClose(IntPtr hComHandle);
[DllImport("F:\\ajm\\Components\\ATM\\New folder (2)\\ATMDev\\ATMCsharp\\inc\\ModuleV188.dll", EntryPoint = "MagRead")]
public static extern int MagRead(IntPtr hComHandle, byte[] MagData, ref byte lenth);
[DllImport("F:\\ajm\\Components\\ATM\\New folder (2)\\ATMDev\\ATMCsharp\\inc\\ModuleV188.dll", EntryPoint = "MagRead")]
public static extern int CarderCheckSt(IntPtr hComHandle, ref byte PewSt, ref byte CardType, ref byte CardSt, ref byte PowerSt);
public Form1()
{
InitializeComponent();
}
public void button1_Click(object sender, EventArgs e)
{
IntPtr port = CommOpenWithBaut(1, 9600);
Byte[] MagData = {128};
byte lenth = 128;
byte PewSt = 0 ;
byte CardType = 0;
byte CardSt = 0;
byte PowerSt = 0;
// CarderCheckSt(port, ref PewSt, ref CardType, ref CardSt, ref PowerSt);
MagRead(port, MagData, ref lenth);
MessageBox.Show($"rc");
MessageBox.Show($"read");
CommClose(port);
}

Related

How to call C ++ method in C # using Invoke

Am I doing it right? I have two projects in parallel, the first is code that was made in C ++ and the second project (Console made in AspNetCore v3.1) is the attempt to call the method that is in C ++ code.
I need to call the C ++ method "Decrypt" in the C # project. How do I do that?
C++ code
#include <stdlib.h>
#include <string.h>
#include<windows.h>
#include "bascript.hpp"
extern "C"
int FAR PASCAL _export
Decript( const LPSTR name, const LPSTR passwordWithCript,
LPSTR passwordWithoutCript, unsigned int sizeSpaceRetorn ) {
LPSTR result = lpDecript( name, passwordWithCript);
if ( sizeSpaceRetorn < strlen(result) )
return 0;
strcpy( passwordWithoutCript, result );
delete result;
return 1;
}
C#
class Program
{
[DllImport(#"C:\MS\VS\TesteDLLCentura\TesteDLLCentura\bin\Debug\netcoreapp3.1\Sises.DLL", CharSet = CharSet.Auto, EntryPoint = "Decript")]
private static extern string Decript(string name, string passwordWithCript, string passwordWithoutCript, uint sizeSpaceRetorn);
static void Main(string[] args)
{
string retorno = Decript("<user>", "<cript_password>", "", 0);
Console.WriteLine(retorno);
Console.ReadLine();
}
}
You can return a pointer from native world (C/C++, etc.) as long as you use a .NET compatible memory allocator. On Windows, that would be the COM Allocator.
So here are 3 ways to return a string: Ansi, Unicode and BSTR (unicode). Note: you should avoid using Ansi on Windows.
C++ side:
extern "C" __declspec(dllexport) void* DecryptA(const char* name, const char* password)
{
char str[] = "hello ansi world";
int size = (lstrlenA(str) + 1) * sizeof(char); // terminating zero
// use .NET compatible allocator
void* buffer = CoTaskMemAlloc(size);
CopyMemory(buffer, str, size);
return buffer;
}
extern "C" __declspec(dllexport) void* DecryptW(const wchar_t* name, const wchar_t* password)
{
wchar_t str[] = L"hello unicode world";
int size = (lstrlenW(str) + 1) * sizeof(wchar_t); // terminating zero
// use .NET compatible allocator
void* buffer = CoTaskMemAlloc(size);
CopyMemory(buffer, str, size);
return buffer;
}
extern "C" __declspec(dllexport) BSTR DecryptBSTR(const wchar_t* name, const wchar_t* password)
{
wchar_t str[] = L"hello BSTR world";
// use .NET compatible allocator and COM coolness
return SysAllocString(str);
}
C# side:
[DllImport("mydll", CharSet = CharSet.Ansi)]
private static extern string DecryptA(string name, string password);
[DllImport("mydll", CharSet = CharSet.Unicode)]
private static extern string DecryptW(string name, string password);
[DllImport("mydll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string DecryptBSTR(string name, string password);
...
static void Main()
{
Console.WriteLine(DecryptA("name", "password"));
Console.WriteLine(DecryptW("name", "password"));
Console.WriteLine(DecryptBSTR("name", "password"));
}
Your C++ function does not return a string or equivalent. It returns an int result (success or failure), and the actual result goes in the passwordWithoutCript buffer.
So you need to create the buffer and pass it in.
Because you are using LPSTR on the C++ side, you need CharSet.Ansi:
[DllImport(#"C:\MS\VS\TesteDLLCentura\TesteDLLCentura\bin\Debug\netcoreapp3.1\Sises.DLL", CharSet = CharSet.Ansi, EntryPoint = "Decript")]
private static extern int Decript(string name, string passwordWithCript, StringBuilder passwordWithoutCript, uint sizeSpaceRetorn);
static void Main(string[] args)
{
var sb = new StringBuilder(1000); // or whatever size
if(Decript("<user>", "<cript_password>", sb, sb.Length) == 1)
Console.WriteLine(retorno);
Console.ReadLine();
}

How to use the catboost C API in dotnet?

I am trying to use catboost C API in C#. Below is the working code in C:
#include <stdio.h>
#include "c_api.h"
int main(int argc, char** argv){
float floatFeatures[3] = {96.215, 1.595655e+09, 3000};
char* catFeatures[0];
double result[1];
ModelCalcerHandle* modelHandle = ModelCalcerCreate();
// LoadFullModelFromFile is time consuming
if (!LoadFullModelFromFile(modelHandle, "../../test_catboost_model.cbm")) {
printf("LoadFullModelFromFile error message: %s\n", GetErrorString());
}
// CalcModelPredictionSingle is fast
if (!CalcModelPredictionSingle(modelHandle,
&floatFeatures, 3,
&catFeatures, 0,
&result, 1
)) {
printf("CalcModelPrediction error message: %s\n", GetErrorString());
}
ModelCalcerDelete(modelHandle);
printf("model score is %.20f", result[0]);
return 0;
}
And below is my attempt to do the same thing in C# (dotnet core on Linux), but it does not work... When I run "dotnet run" just no output no error message.
class Program
{
static void Main(string[] args)
{
var floatFeatures = new float[] { 96.215f, 1.595655e+09f, 3000 };
var catFeatures = new string[0];
var results = new double[1];
var modelHandle = ModelCalcerCreate();
if (!LoadFullModelFromFile(modelHandle, "{absolute path to the same model}/test_catboost_model.cbm"))
{
Console.WriteLine($"Load model error: {GetErrorString()}");
}
if (!CalcModelPredictionSingle(modelHandle, floatFeatures, 3, catFeatures, 0, out results, 1))
{
Console.WriteLine($"Predict error : {GetErrorString()}");
}
Console.WriteLine($"Model score is {results[0]}");
}
[DllImport("catboostmodel", EntryPoint = "ModelCalcerCreate")]
private static extern IntPtr ModelCalcerCreate();
[DllImport("catboostmodel", EntryPoint = "GetErrorString")]
private static extern string GetErrorString();
[DllImport("catboostmodel", EntryPoint = "LoadFullModelFromFile")]
private static extern bool LoadFullModelFromFile(IntPtr modelHandle, string fileName);
[DllImport("catboostmodel", EntryPoint = "CalcModelPredictionSingle")]
private static extern bool CalcModelPredictionSingle(
IntPtr modelHandle,
float[] floatFeatures, ulong floatFeaturesSize,
string[] catFeatures, ulong catFeaturesSize,
out double[] result, ulong resultSize
);
}
The relevant C header file is like below. The entire file is available on github.
#if defined(_WIN32) && !defined(CATBOOST_API_STATIC_LIB)
#ifdef _WINDLL
#define CATBOOST_API __declspec(dllexport)
#else
#define CATBOOST_API __declspec(dllimport)
#endif
#else
#define
CATBOOST_API
#endif
typedef void ModelCalcerHandle;
CATBOOST_API ModelCalcerHandle* ModelCalcerCreate();
CATBOOST_API const char* GetErrorString();
CATBOOST_API bool LoadFullModelFromFile(
ModelCalcerHandle* modelHandle,
const char* filename);
CATBOOST_API bool CalcModelPredictionSingle(
ModelCalcerHandle* modelHandle,
const float* floatFeatures, size_t floatFeaturesSize,
const char** catFeatures, size_t catFeaturesSize,
double* result, size_t resultSize);
Any suggestions are appreciated. Thank you!
It turns out I should not use the "out" keyword before "double[] result" in CalcModelPredictionSingle's signature, removing that fix the problem.
Below works.
[DllImport("catboostmodel")]
private static extern bool CalcModelPredictionSingle(
IntPtr modelHandle,
float[] floatFeatures, ulong floatFeaturesSize,
string[] catFeatures, ulong catFeaturesSize,
double[] result, ulong resultSize
);

Call Cygwin GCC DLL in C# hangs on malloc

I try to make a DLL out of a C/C++ Program that was written for Linux. I got it running in the Cygwin shell and made an interface for the DLL. I also used this to call the function but now the DLL hangs when it wants to allocate memory in a c file.
I broke the problem down to this simple program:
The malloctest.cpp locks like this
#include <stdlib.h>
#include "test.h"
extern "C" __declspec(dllexport) int malloctest();
int malloctest(){
int check = test();
return check;
}
test.h
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
int test();
#ifdef __cplusplus
}
#endif
test.c
#include "test.h"
int test(){
int * array = malloc(42 * sizeof(int));
free(array);
return 42;
}
I compiled it like this in the Cygwin shell
gcc -c malloctest.cpp test.c
gcc -shared -o malloctest.dll malloctest.o test.o
and I called it in C# like this:
class Program
{
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr LoadLibrary(string lpFileName);
public delegate int test();
static void Main(string[] args)
{
IntPtr pcygwin = LoadLibrary("cygwin1.dll");
IntPtr pcyginit = GetProcAddress(pcygwin, "cygwin_dll_init");
Action init = (Action)Marshal.GetDelegateForFunctionPointer(pcyginit, typeof(Action));
init();
IntPtr libptr = LoadLibrary("malloctest.dll");
IntPtr funcptr = GetProcAddress(libptr, "malloctest");
test malloctest = (test)Marshal.GetDelegateForFunctionPointer(funcptr, typeof(test));
int check = malloctest(); //hold
Console.WriteLine(check);
}
}
Now it just hangs without an Error.
It works if I use malloc in malloctest.cpp but as soon as I call a C file that uses malloc the dll just hangs.
Edit:
this is my real interface.h
extern "C" {
typedef struct det det_t;
struct det{
int id;
int hamming;
float goodness;
float decision_margin;
double c[2];
double lu[2];
double ld[2];
double ru[2];
double rd[2];
};
__declspec(dllexport) int scanJPGfromBUF(det_t * dets, uint8_t *, char *);
__declspec(dllexport) int test ();
}
Try changing the delegate to:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int test();
The default for .NET should be Stdcall, but the default in C is Cdecl.
I would translate the C struct to:
struct det
{
int id;
int hamming;
float goodness;
float decision_margin;
double c1;
double c2;
double lu1;
double lu2;
double ld1;
double ld2;
double ru1;
double ru2;
double rd1;
double rd2;
}
So I would remove all the arrays and transform them to multiple elements. The signature of the C method can be translated in multiple ways in C#.
Each parameter with a * could be a ref, a out or a [] (but not a ref [])... So it could be:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int scanJPGfromBUF(ref det_t dets, byte[] bytes1, byte[] chars1);

How to DllImport char*

I have a c++ function like this:
myExport.h
extern "C" { __declspec(dllexport) const int Run(char *input, char *output, int *length); }
myExport.cpp
const int Run(char *input, char *output, int *length) {
std::ostringstream value;
value
<< "FOO" << "|"
<< "BAR" << "|";
auto str = value.str();
auto i = stdext::checked_array_iterator<char*>(output, str.length());
std::copy(str.begin(), str.end(), i);
output[str.length()] = '\0';
return 1;
}
And in C# I have:
myImport.cs
[DllImport("MyExport.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private static extern int Run(
[MarshalAs(UnmanagedType.LPStr)]string input,
StringBuilder output,
ref int length);
public static string Execute(string input)
{
var length = 1024;
var output = new StringBuilder(1024);
var result = Run(input, output, ref length);
return output.ToString();
}
However, the output buffer is always empty. What am I doing wrong?
Since the type is char * for the second parameter, and the DLL function will fill in the buffer that's passed, the C# declaration should be as follows:
[MarshalAs(UnmanagedType.LPStr)]System.Text.StringBuilder output

C# P/Invoke and array of structs containing byte arrays

I need to invoke a native DLL from C# code. As I am not very familiar with C/C++, I can't figure out how a structure defined in C should be declared in C# so it can be invoked. The problem is that two parameters seems to be an array of structs, which I don't know how to declare this in C# (see last code block):
c++ header file:
typedef enum
{
OK = 0,
//others
} RES
typedef struct
{
unsigned char* pData;
unsigned int length;
} Buffer;
RES SendReceive(uint32 deviceIndex
Buffer* pReq,
Buffer* pResp,
unsigned int* pReceivedLen,
unsigned int* pStatus);
c# declaration:
enum
{
OK = 0,
//others
} RES
struct Buffer
{
public uint Length;
public ??? Data; // <-- I guess it's byte[]
}
[DllImport("somemodule.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint SendReceive(
uint hsmIndex,
uint originatorId,
ushort fmNumber,
??? pReq, // <-- should this be ref Buffer[] ?
uint reserved,
??? pResp, // <-- should this be ref Buffer[] ?
ref uint pReceivedLen,
ref uint pFmStatus);
in an equivalent java client, i found that the parameter is not just one Buffer but an array of Buffers. In C# it would look like this:
var pReq = new Buffer[]
{
new Buffer { Data = new byte[] { 1, 0 }, Length = (uint)2 },
new Buffer {Data = requestStream.ToArray(), Length = (uint)requestStream.ToArray().Length },
//according to the header file, the last item must be {NULL, 0}
new Buffer { Data = null, Length = 0 }
};
var pResp = new Buffer[]
{
new Buffer { Data = new byte[0x1000], Length = 0x1000 },
//according to the header file, the last item must be {NULL, 0}
new Buffer { Data = null, Length = 0x0 }
};
This seems strange to me because the extern C method does have a pointer to a Buffer struct (Buffer*) and not a pointer to a Buffer array (Buffer[]*).
How do I need to define the Struct in C# and the parameter types of the extern method?
Any help appreciated, Thanks.
Firstly your struct has the parameters in the wrong order. And the byte array needs to be declared as IntPtr with manual marshalling:
struct Buffer
{
public IntPtr Data;
public uint Length;
}
The p/invoke should be:
[DllImport("MyNativeDll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern RES SendReceive(
uint deviceIndex,
[In] Buffer[] pReq,
[In, Out] Buffer[] pResp,
out uint pReceivedLen,
out uint pStatus
);
The byte array needs to be IntPtr so that the struct is blittable. And that's needed so that the array parameters can be declared as Buffer[].
It's going to be a bit of a pain doing the marshalling of the byte arrays. You'll want to use GCHandle to pin the managed byte arrays, and call AddrOfPinnedObject() to get the address of the pinned array for each struct in your arrays of structs. It will be worth your while writing some helper functions to make that task less painful.
Your method signature in c# should be something like:
[DllImport("MyNativeDll.dll")]
public static extern RES SendReceive (uint32 deviceIndex, ref Buffer pReq, ref Buffer pResp, ref uint pReceivedLen, ref uint pStatus);
See this project, it might hel you in the future so generate native calls from .net
http://clrinterop.codeplex.com/releases/view/14120
Based on the C++ header but without testing, have a look at the following code:
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace WindowsFormsApplication1
{
public class Class1
{
public struct Buffer
{
[MarshalAs(UnmanagedType.LPStr)]
public StringBuilder pData;
public uint length;
}
[DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
static extern int LoadLibrary(string lpLibFileName);
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
static extern IntPtr GetProcAddress(int hModule, string lpProcName);
[DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
static extern bool FreeLibrary(int hModule);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal delegate IntPtr SendReceive(
uint deviceIndex,
ref Buffer pReq,
ref Buffer pResp,
uint pReceivedLen,
uint pStatus);
public void ExecuteExternalDllFunction()
{
int dll = 0;
try
{
dll = LoadLibrary(#"somemodule.dll");
IntPtr address = GetProcAddress(dll, "SendReceive");
uint deviceIndex = 0;
Buffer pReq = new Buffer() { length = 0, pData = new StringBuilder() };
Buffer pResp = new Buffer() { length = 0, pData = new StringBuilder() };
uint pReceivedLen = 0;
uint pStatus = 0;
if (address != IntPtr.Zero)
{
SendReceive sendReceive = (SendReceive)Marshal.GetDelegateForFunctionPointer(address, typeof(SendReceive));
IntPtr ret = sendReceive(deviceIndex, ref pReq, ref pResp, pReceivedLen, pStatus);
}
}
catch (Exception Ex)
{
//handle exception...
}
finally
{
if (dll > 0)
{
FreeLibrary(dll);
}
}
}
}
}

Categories

Resources