Bidirectional communication between unmanaged C++ (no CLR) and managed C# - c#

I am working with a C++ MFC plug-in dll which is automatically launched from an off-the-shelf application at runtime. I need to modify the plugin to support bidirectional communication with a .NET based C# application.
I have access to the plugin source code but I am not able to change the C++ project to use /clr. I get a runtime error from the off-the-shelf application:
'r6033 attempt to use msil code from this assembly during native code initialization'
I have tried to use "#pragma unmanaged" around the Main method with no luck. So I decided to go the COM route.
I have successfully created a COM based C# dll with a UI and methods implemented which I can call from C++. That gets me one direction. I need to implement a way of making calls to the C++ plugin from the C# application. I started down the path of events. I created an event in C# which are raised on the C# side from the new UI but I was not able to figure out how to create and hook to the events on the C++ side. Any help would be appreciated.
Here is the C# COM Code:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.IO;
namespace MyInterop
{
[Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")]
public interface IMyDotNetInterface
{
void ShowCOMDialog();
}
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("63058ba6-5675-43d6-b38b-fd261fe64950")]
public interface IIntelliWaveEvents
{
[DispId(1)]
void CallIWave(string a_IWaveCommand);
}
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IIntelliWaveEvents))]
[Guid("0490E147-F2D2-4909-A4B8-3533D2F264D0")]
public class MyDotNetClass : IMyDotNetInterface
{
public IIntelliWaveEvents callbackObject;
// Need a public default constructor for COM Interop.
public MyDotNetClass()
{ }
public void ShowCOMDialog()
{
MainForm frm = new MainForm()
{
StartPosition = FormStartPosition.CenterScreen,
TopMost = true
};
frm.CallIWave += new MainForm.CallIWaveEventHandler(frm_CallIWave);
frm.Show();
}
void frm_CallIWave(object sender, Events.IWaveEvents.CallIWaveEventArgs e)
{
string filename = #"C:\Companies\ComEvent.txt";
File.WriteAllText(filename, "COM Event Called at " + DateTime.Now);
//Pass the event to c++
if (callbackObject != null)
{
callbackObject.CallIWave(e.Message);
}
}
}
}
Here is the interface WaveDLL_PAI.cpp code:
#include "stdafx.h"
#include "WaveDLL_PAI.h"
#include "IUserPrograms1.h"
using namespace std;
#import "C:\Program Files\SampleCOM\com.MyInterop.tlb" named_guids raw_interfaces_only
using namespace MyInterop;
//------------------------------------------------------------------------
// This function must exist in all PlugIn(s).
// It creates an instance of the API class and returns a pointer to it.
HRESULT CreateInstance(void **p)
{
*p = new CWaveDLL_PAI();
return (*p != NULL);
}
//---------------------------------------------------------------------------------------------------
// Initialize native variables here
CWaveDLL_PAI::CWaveDLL_PAI() : CWaveDLL_BaseI()
{
}
//---------------------------------------------------------------------------------------------------
// deallocate any memory here
CWaveDLL_PAI::~CWaveDLL_PAI()
{
Release();
}
//---------------------------------------------------------------------------------------------------
// This function releases any memory allocated for this class
ULONG CWaveDLL_PAI::Release()
{
return 0;
}
//---------------------------------------------------------------------------------------------------
// This function must be used to create the class including memory allocation.
// Allocate memory or objects here only.
// Do not display any window or dialog here
ULONG CWaveDLL_PAI::Create(CWnd *pParent, void *a, void *b, void *c)
{
CoInitialize(NULL); //Initialize all COM Components
MyInterop::IMyDotNetInterfacePtr pDotNetCOMPtr;
HRESULT hRes = pDotNetCOMPtr.CreateInstance(MyInterop::CLSID_MyDotNetClass);
if (hRes == S_OK)
{
BSTR str;
pDotNetCOMPtr->ShowCOMDialog(); //call .NET COM exported function ShowDialog ()
}
CoUninitialize(); //DeInitialize all COM Components
return true;
}
//---------------------------------------------------------------------------------------------------
// This function gives the PlugIn access to data and controls
// This function must be defined and have the code listed below.
long CWaveDLL_PAI::SetData(CUserControl *ctrl, SIntelliWaveDataExt *data, LPCTSTR path)
{
CWaveDLL_BaseI::SetData(ctrl,data,path);
return true;
}
Here is the interface WaveDLL_PAI.h Code:
#include "WaveDLL_BaseI.h"
class AFX_EXT_CLASS CWaveDLL_PAI : public CWaveDLL_BaseI
{
public:
int MyVariable;
CWaveDLL_PAI();
~CWaveDLL_PAI();
int MyFunction(int hello = 0);
ULONG Release();
ULONG Create(CWnd *pParent=0, void *a = 0, void *b = 0, void *c = 0);
long SetData(CUserControl *ctrl, SIntelliWaveDataExt *data, LPCTSTR path = 0);
long Execute(LPCTSTR cmd, void *a = 0, void *b = 0, void *c = 0);
};
Here is the WaveDLL_PA.cpp Code:
#include "stdafx.h"
#include <afxdllx.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
HINSTANCE hInstanceOptic01;
static AFX_EXTENSION_MODULE WaveDLL_PADLL = { NULL, NULL };
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
// Remove this if you use lpReserved
UNREFERENCED_PARAMETER(lpReserved);
hInstanceOptic01 = hInstance;
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("WaveDLL_PA.DLL Initializing!\n");
// Extension DLL one-time initialization
if (!AfxInitExtensionModule(WaveDLL_PADLL, hInstance))
return 0;
// Insert this DLL into the resource chain
// NOTE: If this Extension DLL is being implicitly linked to by
// an MFC Regular DLL (such as an ActiveX Control)
// instead of an MFC application, then you will want to
// remove this line from DllMain and put it in a separate
// function exported from this Extension DLL. The Regular DLL
// that uses this Extension DLL should then explicitly call that
// function to initialize this Extension DLL. Otherwise,
// the CDynLinkLibrary object will not be attached to the
// Regular DLL's resource chain, and serious problems will
// result.
new CDynLinkLibrary(WaveDLL_PADLL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("WaveDLL_PA.DLL Terminating!\n");
// Terminate the library before destructors are called
AfxTermExtensionModule(WaveDLL_PADLL);
}
return 1; // ok
}

Related

How to call C++ code, and the code it calls, from C#

TL;DR
I have legacy c++ code which does stuff (sometimes returns stuff), calls other cpp code, but is not a full class/obj. This code I cannot alter. I am making fresh c# code which I am looking to call the c++ code from. I don't understand whether to create a dll that calls the original legacy c++, or create a CLR?? which also calls the legacy cpp. Below I have example code that I have implemented (with problems).
Main
I have legacy.cpp and legacy.h which I can not alter.
This is not a class/object and only has public functions, values, and #defines.
legacy.cpp and .h both #include other files and cpp libraries to do its job.
I have a new project where I can add C#, C++ code
I am having trouble understanding what I need to do/research in order to call any of the functions defined in legacy.cpp (or the values/defines) from within my new C# code.
Some of what I have looked at include
Managed CLR wrappers
https://drthitirat.wordpress.com/2013/06/03/use-c-codes-in-a-c-project-wrapping-native-c-with-a-managed-clr-wrapper/
https://web.archive.org/web/20140806022309/http://blogs.msdn.com/b/borisj/archive/2006/09/28/769708.aspx
DLLs??
How to call C++ DLL in C#
CLR
I have currently tried to create a CLR (thought I feel like it is not what I need in this situation), but I had problems in the clr_wrapper.cpp, it could not find the reference to foo()
//Wrapper.cpp
#include "Wrapper.h"
#include "abs\path\to\legacy\code\legacy.h"
#include "abs\path\to\legacy\code\legacy.cpp"
int foo_Wrapper()
{
return foo(); //int foo() is declared in legacy.h and defined in legacy.cpp
}
#pragma once
#include "abs\path\to\legacy\code\legacy.h"
#include "abs\path\to\legacy\code\legacy.cpp"
using namespace System; //What things use this?
//Can I just prepend System:: to whatever needs it?
namespace Wrapper {
public ref class Wrapper
{
public:
int foo_Wrapper();
};
}
the foo_Wrapper() is not able to call foo().
My confusion with this method is that it looks like I would need to make the clr wrapper an object class with member functions that will be called as needed. Leading to a syntax of obj.foo(). Is this what needs to be done if I chose to do some sort of CLR wrapper?
DLL
I have also looked at making this all a dll like in (How to call C++ DLL in C#)
However I am confused on setting this up. My current idea is to have a cpp dll call the original cpp (ie create legacyDll.dll which would make calls to foo(), then my main c# would call the __declspec(dllexport) functions defined within extern "C" {}
current setup (from "How to call c dll in c sharp")
dllmain.cpp
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <iostream>
#include <Windows.h>
using namespace std;
extern "C"
{
__declspec(dllexport) void bar_Dll()
{
cout << "calling bar() in legacy code" << endl;
}
__declspec(dllexport) int foo_Dll()
{
cout << "calling foo() in legacy code" << endl;
//realistically I would have,
//return foo()
}
}
Class1.cs
using System;
using System.Runtime.InteropServices;
namespace Test_DLL_Calling
{
class Class1
{
[DllImport("dllmain.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void bar_Dll();
[DllImport("dllmain.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int foo_Dll();
public static void Main(string[] arg)
{
bar_Dll(); //The specified module could not be found (Exception)
Console.WriteLine(foo_Dll()); //Im guessing this will also ^^
}
}
}
This part I don't follow. What and why are the attributes done the way they are?
Alright, so you have a header and cpp which you need to use. In order to use it you have to have make the c++ into C code. This is pretty much what you see in the DLL example code that you showed. However, I do suggest that you remove the includes from the header as I'm unsure how that would translate to the C code. Put the includes in the cpp files instead.
I find it rather difficult to answer this questions other than just showing a whole bunch of example code. Full code in: https://github.com/ze413X/Cpp-Code-Gems/ Where "CSharpExampleUsingCpp" calls from MainWindow.xaml.cs the "DLLExample" which uses the file in the directory includes and source.
DLL:
The header which is exposing functions to be used:
#pragma once
#define DLLExport _declspec(dllexport)
extern "C" DLLExport void __cdecl GetCppText(char* str, int* strLength);
extern "C" DLLExport void __cdecl DLLPrint();
.cpp
#include "../includes/DLLExampleCode.h"
#include <string>
#include <iostream>
void __cdecl GetCppText(char* str, int* strLength)
{
std::string text = "This is called from within the DLL.\0";
if (*strLength < text.length() || str == nullptr)
{
return;
}
memset((void*)str, 0, (*strLength) * sizeof(char));
strcpy_s(str, *strLength,text.c_str());
*strLength = text.length();
}
void __cdecl DLLPrint()
{
std::cout << "This is printed from inside DLLExample.dll.\n";
}
C#:
using System.Runtime.InteropServices;
namespace CSharpExampleUsingCpp
{
public partial class MainWindow : Window
{
const string PATH = "DLLExample.dll";
[DllImport(PATH, CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern void GetCppText(byte[] str, out System.Int32 strLength);
....
private void CppInteropButton_Click(object sender, RoutedEventArgs e)
{
System.Int32 size = 256;
System.Byte[] str = new byte[size];
for (int i = 0; i < size; i++)
{
str[i] = (byte)'1';
}
GetCppText(str, out size);
string result = System.Text.Encoding.UTF8.GetString(str, 0, size);
CppInteropButtonTextBox.Text = result;
}
Although, rereading my solution of obtaining a string might not be the best way of doing it. You could probably marshal that thing to avoid all this stupid char* conversions. I probably had some good reason at that point in time when I wrote it. That should be much easier to google though.

AccessViolationException when calling C++/CLI wrapper from C#

I am trying to create a C++/CLI wrapper for passing class objects from unmanaged C++ DLL into managed C# code (which subsequently displays the content of the objects on web pages). I have this function in the unmanaged C++ code:
ProbeState _cdecl ManagerAPI::getProbeState()
{
ProbeState ps = psdao.getLastProbeStateByProbeId(1);
return ps;
}
I call the function in the C++/CLI wrapper:
using namespace System;
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "../ManagerApp/ProbeState.h"
typedef ProbeState(*PSFunc)(void);
public ref class ManagerAPIWrapper
{
private:
HINSTANCE managerApp;
public:
ManagerAPIWrapper()
{
managerApp = LoadLibrary(L"ManagerApp.dll");
}
System::String^ testFunc()
{
PSFunc psFunc = (PSFunc)GetProcAddress(managerApp, "?getProbeState#ManagerAPI##QAA?AVProbeState##XZ");
ProbeState *ps = new ProbeState(psFunc());
System::String ^s = gcnew System::String(ps->getName().c_str());
delete ps;
return s;
}
};
And finally I call the wrapper from my C# controller:
ManagerAPIWrapper.ManagerAPIWrapper wrapper = new ManagerAPIWrapper.ManagerAPIWrapper();
ViewBag.DllMessage = wrapper.testFunc();
It always throws an exception on the line ProbeState *ps = new ProbeState(psFunc());
Strange thing, though, is when I compile the C++/CLI wrapper as a console application with added main function:
int _tmain(int argc, _TCHAR* argv[])
{
ManagerAPIWrapper::ManagerAPIWrapper wrapper;
System::Console::WriteLine(wrapper.testFunc());
getchar();
return 0;
}
This code works just fine and prints out the name of the state retrieved from the database by the C++ DLL. How come the C++/CLI works in console app and throws an exception when called from C#?
P.S.: The wrapper is compiled with /clr option. When I compiled the wrapper with /clr:pure, the exception was the same as with the C# call. Does it mean that when the wrapper is compiled within and called from C# app, it takes the pure option?
The wrapper is meant to convert the data between C++ and C#, so according to my opinion it should not be compiled with more strict options in the C# app. Is there any way to tell the C# compiler that this assembly contains mixed code?
OK, I finally got through this. After many hours spent with try&fail way of finding a solution, I tried to call a function from the unmanaged DLL directly from the C# code first, and then called a constructor of the wrapper, which succeeded in the LoadLibrary call. Code in the C# controller now looks like this:
[DllImport("C:\\ManagerApp.dll", CharSet = CharSet.Unicode,
EntryPoint = "?initFunc#ManagerAPI##QAEHXZ")]
private static extern int initFunc();
public ActionResult APITest()
{
ViewBag.Message = "API output test page.";
if (initFunc() == 0)
{
ViewBag.Error = "Could not initialize the library.";
return View();
}
ManagerAPIWrapper.ManagerAPIWrapper wrapper = new ManagerAPIWrapper.ManagerAPIWrapper();
ViewBag.DllMessage = wrapper.testFunc();
return View();
}
I am thinking it might help to add a dependency to the wrapper DLL on the unmanaged DLL and therefore get rid of the necessity of calling the initFunc.

Problems Interoping C++ (DLL) with C# console app

I'm trying to interop C++ DLL (dissasembly benefits) with C# app, so I created a C++ Win32 Project (DLL) with export symbols like this:
LicensePolicy32.h:
// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the LICENSEPOLICY32_EXPORTS
// symbol defined on the command line. This symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// LICENSEPOLICY32_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef LICENSEPOLICY32_EXPORTS
#define LICENSEPOLICY32_API __declspec(dllexport)
#else
#define LICENSEPOLICY32_API __declspec(dllimport)
#endif
LICENSEPOLICY32_API char* GetXmlTokenNode(void);
LicensePolicy32.cpp:
#include "stdafx.h"
#include "LicensePolicy32.h"
bool GetLicense()
{
DWORD dwType = REG_SZ;
HKEY hKey = 0;
char value[1024];
DWORD value_length = 1024;
LPCWSTR subkey = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\WSESecurityPolicy";
LONG openReg = RegOpenKeyEx(HKEY_LOCAL_MACHINE,subkey,0, KEY_WOW64_64KEY | KEY_QUERY_VALUE, &hKey);
if (openReg==ERROR_SUCCESS)
{
return true;
}
else
{
return false;
}
LONG getReg = RegQueryValueEx(hKey, L"WSEHostProcessID", NULL, &dwType, (LPBYTE)&value, &value_length);
if (getReg==ERROR_SUCCESS)
{
return true;
}
else
{
return false;
}
}
LICENSEPOLICY32_API char* GetXmlTokenNode()
{
char *orig;
bool resultLicense = GetLicense();
if (!resultLicense)
{
return "";
}
else
{
return "/{0}:Envelope/{0}:Header";
}
}
My C# test code is as follows:
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace TestDllConsoleApplication
{
class Program
{
[STAThread]
static void Main(string[] args)
{
Test testObj = new Test();
}
}
public class Test
{
[MarshalAs(UnmanagedType.LPStr)]
public string result;
[DllImport(#"D:\Proyectos\NET\Dll\LicensePolicy32\Release\LicensePolicy32.dll", CharSet=CharSet.Ansi, EntryPoint = "GetXmlTokenNode")]
public static extern string GetXmlTokenNode();
public Test()
{
try
{
result = GetXmlTokenNode();
result += " result";
}
catch (DllNotFoundException exDll)
{
string error = "Dll no encontrado";
}
catch (BadImageFormatException exBad)
{
string error = "Plataforma Ensamblado incompatible";
}
catch (Win32Exception exWin32)
{
string error = "Error general de Win32";
}
catch (EntryPointNotFoundException exPoint)
{
string error = "No encontró punto de entrada al método";
}
catch (Exception ex)
{
string error = "Error otros";
}
}
}
}
Path to DLL is good, however when I run C# test project throws EntryPointNotFoundException.
I'd appreciate your help, thanks
These are not the droids you are looking for. Your c++ names will be decorated and the actual name will not be GetXmlTokenNode. To avoid this, use extern "C" as part of the signature for the methods you want to export. This will avoid name decoration.
You may find this whitepaper useful.
C++ compiler add a suffix to any function name. The suffix defines the parameter types and orders - that way the compiler can handle overloading.
To avoid it you can compile using C (change the cpp suffix to C, and if you don't have special C++ features in the code VS will understand what you want to do).
Alternatively find the real name of the native DLL function and use it as the entry point.

CoCreateInstance() -858993460 (in comip.h) C++

I will spend a little time explaining my project stucture:
There are three dlls:
mclController.dll - a third party dll written in C# to control the hardware..
MCLWrapper.dll - I wrote this ll in C# such that it will be working as a COM to expose the mclControl.dll to a native C++ dll.
ThorDetectorSwitch.dll - I wrote this dll with native C++.
Structure:
The ThorDetectorSwitch.dll calls the MCLWrapper.dll which wraps mclController.dll.
I am implementing a small testing console application in C++, TDSTest.exe to call ThorDetecttorSwitch.dll.
So it basically works like this: TDSTest.exe -> ThorDetectorSwitch.dll -> MCLWrapper -> mclController.dll
Some code:
-How TDSTest.exe (Windows console application, built with x64 configuration) calls ThorDetectorSwitch.dll:
#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <math.h>
#include <windows.h>
#include "TDSTest.h"
typedef long (*TDSFindDevices)(long&);
typedef long (*TDSGetParam)(const long, double&);
typedef long (*TDSTeardownDevice)();
typedef long (*TDSStartPosition)();
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
if (argc < 2)
{
cout<<"This is ThorDetecttorSwitch test program."<<endl;
return 1;
}
HINSTANCE hInst = LoadLibrary(_T(".\\Modules_Native\\ThorDetectorSwitch.dll"));
if( hInst == NULL )
{
DWORD err = GetLastError();
cout<<"Error loading ThorDetectorSwitch.dll. Program exiting..."<<endl;
return 1;
}
}
-Constructor of the ThorDetectorSwitch.dll EDITTED! on 06/15/2013, Central Time 19:41
ThorDetectorSwitch::ThorDetectorSwitch() :_mcSwitch(ComHelper(__uuidof(MCLControlClass)))
{
CoInitialize(NULL);
MCLWrapper::MCLControlPtr mclSmartPtr;
HRESULT hr = CoCreateInstance(__uuidof(MCLWrapper::MCLControlClass), NULL, CLSCTX_ALL, __uuidof(MCLWrapper::MCLControl), (void**)&mclSmartPtr); // program breaks right here!!!
_mcSwticth = mclSmartPtr;
_A = WstringToBSTR(L"A");
_B = WstringToBSTR(L"B");
_C = WstringToBSTR(L"C");
_D = WstringToBSTR(L"D");
_deviceDetected = FALSE;
}
The MCLWrapper that makes a COM object
// C# COM wrapper
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using mcl_RF_Switch_Controller64;
using System.Runtime.InteropServices;
// for function reference see miniCircuit RF controller manual
namespace MCLWrapper
{
[Guid("7C312A7C-2E77-4de7-A76F-990F268AB818")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface MCLControl
{
[DispId(1)]
void Connect(string SerialNumber);
[DispId(2)]
void Set_Switch(string SwitchName, int Val);
[DispId(3)]
void Set_SwitchesPort(byte binVal);
[DispId(4)]
void GetSwitchesStatus(int statusRet);
[DispId(5)]
void Disconnect();
};
[Guid("BEC33A1D-BB98-4332-B326-92D480ECC246"),
ClassInterface(ClassInterfaceType.None)]
public class MCLControlClass : MCLControl
{
private USB_RF_SwitchBox _sb = new USB_RF_SwitchBox();
public void Connect(string SerialNumber)
{
_sb.Connect(ref SerialNumber);
}
public void Set_Switch(string SwitchName, int Val)
{
_sb.Set_Switch(ref SwitchName, ref Val);
}
public void Set_SwitchesPort(byte binVal)
{
_sb.Set_SwitchesPort(ref binVal);
}
public void GetSwitchesStatus(int statusRet)
{
_sb.GetSwitchesStatus(ref statusRet);
}
public void Disconnect()
{
_sb.Disconnect();
}
}
}
My Problem:
When the TDSTest is executed, it first hits
HINSTANCE hInst = LoadLibrary(_T(".\\Modules_Native\\ThorDetectorSwitch.dll"));
then it breaks at:
hr = CoCreateInstance(......) in the ThorDetectorSwitch.cpp
hr = -858993460 is the return;
A few additionals
I am kept be told it was because CoInitialized() did not get called, and that is the reason, but I feel that is not the reason because this ThorDetectorSwitch.dll works perfectly fine with another application, and I beleive I have called CoInitialized() in my code.
I have registered my MCLWrapper.dll with regasm MCLWrapper.dll /tlb:MCLWrapper.tlb /codebase
Debugger output: "Attempting managed execution inside OS Loader lock. Do not attempt to run managed code inside a DllMain or image initalization function since doing so can cause the application of hang."
So right now I have no idea what direction I should go, and I have been struglling with this problem for days. So I really hope someone can give some pointers for me. Thanks!
You need to lazily construct your object instead of have it as a global variable created on DLL load.
Maybe you could have your DLL provide an Initialize() function that would be called by the client? Assuming you can't make your object "not global at all" of course.

How to pass a const unsigned char * from c++ to c#

So I have a function in unmanaged c++ that gets called when some text happens to have "arrived":
#using <MyParser.dll>
...
void dump_body(const unsigned char *Body, int BodyLen)
{
// Need to pass the body to DumpBody, but as what type?
...
MyParser::Parser::DumpBody(???);
}
DumpBody is a static function defined in a C# DLL that should take one parameter of type?
Body holds an array of characters (text) of length BodyLen.
There's obviously some marshalling to be done here but I have no idea how.
Please help.
void dump_body(const unsigned char *body, int bodyLen)
{
// you might want a different encoding...
String ^str = gcnew String((sbyte*)body, 0, bodyLen, gcnew ASCIIEncoding);
MyParser::Parser::DumpBody(str);
}
DumpBody will take a string.
The follwing code describes the situation of communication between managed C# code and a unmanaged C/C++ DLL via PInvoke. Essentially the idea is, that you can pass your C code a C# delegate. The const unsigned char* gets converted to a string with Marshal.PtrToStringAnsi.
C++:
typedef void FncFoo (const char*);
FncFoo callback = 0;
extern "C" {
// this function is called from C# to pass the delegate
void TakeFooCallback(FncFoo f) {
callback = f;
}
}
// C function calling C#
void dump_body(const unsigned char *Body, int BodyLen) {
if(callback) {
callback(Body);
}
}
C#:
class StringC2CS {
delegate void DlgFoo(IntPtr a);
[DllImport("myclib.dll")] // name of the dll
void TakeFooCallback(DlgFoo callback);
// this function gets called by C
void FooImpl(IntPtr a) {
string str = Marshal.PtrToStringAnsi(a);
// use string
}
public StringC2CS() {
// passes a callback to C
TakeFooCallback(new DlgFoo(FooImpl));
}
}
I ended up using Danvil's approach. I wrapped the C code in a C++ dll and then I made another C# dll that referenced the C++ dll and exposed the functionality that I wanted in managed code. So here it is:
C++ dll:
.h File
// Definition of a callback function to be called when some data arrives
typedef void (*FncDlg) (const unsigned char*, int len);
// Declaration of the function that will be exposed in managed code.
// The managed code will use this function to pass in a delegate
extern "C" __declspec(dllexport) void AttachCallback(FncDlg f);
// Declaration of a function that receives data and passes it to managed code
void PassData(const unsigned char *Body, int BodyLen);
.cpp File
// Instantiate a global function pointer to nothing
void (*callback)(const unsigned char*, int len) = 0;
// This is called by C# to pass in a delegate
void AttachCallback(FncDlg f)
{
// Save delegate globally (to be triggered later when we receive data)
callback = f;
}
// This is the function called when data is read from the socket
void PassData(const unsigned char *Body, int BodyLen)
{
if(callback) {
callback(Body, BodyLen);
}
}
C# dll:
public static class Parser
{
public delegate void DlgDumpTipData(IntPtr a, int len);
[DllImport("C++ dll name here", EntryPoint = "AttachCallback", ExactSpelling = true)]
public static extern void AttachCallback(DlgDumpTipData callback);
public static int ConnectAndStartReceive(...)
{
// Attach a callback function (called when a message is received from data feed)
AttachCallback(DumpDataCalback);
...
}
public delegate void MsgHandler(string msg);
// When data arrives from the C library, this event passes it on to C# clients
public static event MsgHandler OnMessageArrived = delegate { };
public static void DumpDataCalback(IntPtr ptr, int len)
{
//string str = Marshal.PtrToStringAnsi(ptr);
string dumpData;
sbyte[] byteArr = new sbyte[len];
for (int i = 0; i < len; i++)
{
// WARNING: if byte > 127 we have a problem when casting from Byte to SByte
// In this case there is no problem, tested with values over 127 and it
// converts fine to Latin-1 using the String overload
byteArr[i] = (sbyte)Marshal.ReadByte(ptr, i);
}
unsafe
{
fixed (sbyte* pbyteArr = byteArr) // Instruct the GC not to move the memory
{
dumpData = new String(pbyteArr, 0, len, Encoding.GetEncoding("ISO-8859-1"));
}
}
// Send data to whoever subscribes to it
OnMessageArrived(dumpData);
GC.Collect(); // This slows things down but keeps the memory usage low
}
}

Categories