1
votes

I want to call a C# function from C++ , via CLI/C++.

C# code

private string _text = " ";

public void setText(string text)
{
    // _text = text;
    _text = "HI World";
}

Ideally setText shall have the commented line only. The _text = "HI World" is an example.

public string getText()
{
    return _text;
}

C++/CLI code

Header :

gcroot<Bridge> _managedObject;

virtual void setText(std::string text);
virtual std::string getText();

CPP file

std::string CStringBridge::getText()
{

// _managedObject = gcnew Bridge(); return (marshal_as(_managedObject->getText())); }

void CStringBridge::setText(std::string text)
{

// _managedObject = gcnew Bridge(); _managedObject->setText(gcnew System::String(text.c_str())); }

IStringBridgeWrapper* IStringBridgeWrapper::CreateInstance(void)
{
return ((IStringBridgeWrapper *)new CStringBridge());
}

Note : When I use the following code

virtual void setText(System::String^ text);
virtual System::String^ getText();

I get the following error 3395

*__declspec(dllexport) cannot be applied to a function with the __clrcall calling convention*

, and so I stuck to std::string

When I use the library from the C++/CLI code, and call from my C++ program, "Hi World" should be printed ; instead nothing gets printed

C++ console application

IStringBridgeWrapper *pBridge = IStringBridgeWrapper::CreateInstance();

pBridge->setText(std::string("I am here"));
pBridge->getText();

I think the string is not being properly passed .

Any ideas to solve it shall be appreciated.

EDIT

I have updated the code after the comments , yet nothing shows up.

gcroot creates a handle, but does not allocate memory for it. But as Bridge has no memory allocated , the application does not work.My code is in the same lines at the article here - http://www.codeproject.com/Articles/10020/Using-managed-code-in-an-unmanaged-application .

2
why are you creating a new instance of Bridge in the CPP/CLI getText function, if I understand correctly, won't that reset the _text object in your C# class?Zaid Amir
You are creating new instances of Bridge in your getText and setText functions. That can of course not work, getText will always return an empty string since you just created the object. Your wrapper needs an instance of Bridge as a member. Check your favorite C++ language book about encapsulation.Hans Passant
I was also thinking that might be a problem. Thanks for pointing it. I have the gcroot<Bridge> _managedObject ; should I use that? ( please see the edited post ) And what do you say about the string I pass in setText ; should I gcnew it.Sujay Ghosh
How are you showing the text on the console? Have you debugged your code and confirmed that the returned string is actually empty?Zaid Amir
@RedSerpent .I did place some Console.Writeline statements, to check the flow , and the flow is as expected - C++ to C++/CLI to C# .Sujay Ghosh

2 Answers

0
votes

I want to call a C# function from C++ , via CLI/C++.

Wait... you want to call a C++ function from C#, right? That's what C++/CLI is good for. Wrapping C++ code to be accessible in managed environments. If you really want to call C# code from C++, you should look into COM registering your C# code. If you use C++/CLI for this, your whole C++ program will be dragged into the .NET world and you could have used C# from the start.

In C++/CLI, your whole public class interface of ref (.NET) classes should consist of only managed types. That would be System::String^ instead of std::string.

0
votes

COM is your friend:

Create a an interface in C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;


namespace CsharpLibrary
{
   // Since the .NET Framework interface and coclass have to behave as 
   // COM objects, we have to give them guids.
   [Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"),
    InterfaceType(ComInterfaceType.InterfaceIsDual)]
   public interface IStringHolder
   {
      String GetText();
      void SetText(String s);
   }
}

Implement the C# interface:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace CsharpLibrary
{
   [Guid("C6659361-1625-4746-931C-36014B146679")]
   public class MyStringHolder : IStringHolder
   {
      String _text;

      public String GetText()
      {
         return this._text;
      }

      public void SetText(String value)
      {
         _text = value;
      }

   }
}

Create and call your C# object from C++

#include <windows.h>
#include <stdio.h>

#pragma warning (disable: 4278)

// To use managed-code servers like the C# server, 
// we have to import the common language runtime:
#import <mscorlib.tlb> raw_interfaces_only


#pragma warning (disable: 4278)

// To use managed-code servers like the C# server, 
// we have to import the common language runtime:
#import <mscorlib.tlb> raw_interfaces_only

#import "..\CsharpLibrary\bin\Debug\CsharpLibrary.tlb" no_namespace named_guids





int main(int argc, char* argv[])
{
   HRESULT hr = S_OK;

   IStringHolder *pStringHolder = NULL;

   //
   // Initialize COM and create an instance of the InterfaceImplementation class:
   //
   CoInitialize(NULL);

   hr = CoCreateInstance(   __uuidof(MyStringHolder),
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           __uuidof(IStringHolder),
                           reinterpret_cast<void**>(&pStringHolder));

   if(SUCCEEDED(hr))
   {
      _bstr_t sHelloWorld = SysAllocString( L"Hello, World" );

      hr = pStringHolder->SetText(sHelloWorld);

      SysFreeString(sHelloWorld);
   }


   //
   // Be a good citizen and clean up COM
   //
   CoUninitialize();

   return hr;
}

On the C# side you have to generate the type library and register the class by a post-build event:

generate the type library: "$(FrameworkSDKDir)bin\NETFX 4.0 Tools\tlbexp.exe" "$(TargetPath)" /out:"$(TargetDir)$(TargetName).tlb"

register the class: C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe "$(TargetPath)"

Enjoy!