2016年5月7日 星期六

Calling C# Methods From C++


        In the previous article, I have demonstrated how to call C/C++ libraries from C#. In this post, I want to deal with the inverse action : call C# method from C++.
       
          Be constrained to  grammar, it is not possible to call  C# from C,  it is   only C++ could achieve this purpose.


       一.  Create the C# library


I create the C# library donned as "CSharpLibrary"



       
And enable the unsafe selection:




The C# code, name as  CSharpLibrary.cs, be :


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

namespace CSharpLibraryNameSpace
{
    // Interface declaration.
    public delegate
        int NativeDelegateType(IntPtr pStrInUnicode, int strlen, IntPtr pArgs);

    [ComVisible(true)]
    public interface ManagedInterface
    {
        int ArraySum(IntPtr pIntArray, int len, ref int sum);

        int CalltheCallbackFun(IntPtr callbackFnPtr, IntPtr pArgs);
    };


    // Interface implementation.
    [ComVisible(true)]
    public class ManagedCSharpClass : ManagedInterface
    {
        public int ArraySum(IntPtr pIntArray, int len, ref int sum)
        {
            Console.Write("{0}\n", 
                System.Reflection.MethodBase.GetCurrentMethod().Name);

            sum = 0;
            unsafe
            {
                int* pArray = (int*)pIntArray;

                for (int i = 0; i < len; i++)
                    sum += pArray[i];
            }
            return 0;
        }

        public int CalltheCallbackFun(IntPtr callbackFnPtr, IntPtr pArgs)
        {
            string str;
            str = "牛羊豬雞貓狗";

            unsafe
            {
                fixed (char* pStr = str)
                {
                    Console.Write("before call callbackFun ptr={0}\n", 
                        callbackFnPtr);
                    //Convert IntPtr to Delegate
                    NativeDelegateType callback =
                        Marshal.GetDelegateForFunctionPointer(callbackFnPtr,
                                                 typeof(NativeDelegateType))
                        as NativeDelegateType;

                    callback(Marshal.StringToHGlobalUni(str), str.Length, pArgs);

                }
            }
            return 0;
        }
    }
}


Note that there is a delegate (callback ) interface for C++ registering.

              In the code I have write some Chinese words in a string, to demonstrate how to pass   Unicode  string  through C# to C++.


二. Prepare a type library (tlb) for C++ linking with.

 In the Build Event part, of  project  setting :


Add the comment line in post-build column :


%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe $(ProjectName)bin\$(ConfigurationName)\$(ProjectName).dll /tlb:$(ProjectName).tlb  /codebase
%SystemRoot%\system32\xcopy.exe /Y $(ProjectName)bin\$(ConfigurationName)\$(ProjectName).tlb $(ProjectName)

The two lines are to generate file CSharpLibrary.tlb for C++ binary linking with.

You could press CTRL + SHIFT + B to build the library, the binary CSharpLibrary.tlb would be present under the project root folder.

Then Close current  VS2005  project.


三. Create a empty C++  project and solution:

Create a blank C++ project, named CCallCCsharp:



After your creating has done, put all the C# library project and code and so forth stuff  under  the  CCallCCsharp folder:


 Add a  file, main.cpp under  project , the code be :


#include <locale.h>
#include <windows.h>

// Import the type library.
#import "CSharpLibrary.tlb" raw_interfaces_only
using namespace CSharpLibrary;

int __stdcall CCallbackFunction(wchar_t *pStrInUnicode, int strlen, void *pArgs)
{
 wprintf(TEXT("%s\n"), pStrInUnicode);
 wprintf(TEXT("%d\n"), *((int*)pArgs));

 return 0;
}/*CCallbackFunction*/


int main(int argc, char *argv[])
{
 HRESULT hr;
 /*Set current unicode*/
 setlocale(LC_ALL,"");

 // Initialize COM.
 hr = CoInitialize(NULL);

 // Create the interface pointer.
 ManagedInterfacePtr CSharpDLLPtr(__uuidof(ManagedCSharpClass));

 {
  long lResult = 0;
  int intArray[10], sum;
  int i;
  for(i = 0; i< 10; i++)
   intArray[i] = i;

  // Call the ArraySum method
  CSharpDLLPtr->ArraySum((long)&intArray[0], 
   sizeof(intArray), (long*)&sum, &lResult);
  wprintf(L"The result is %d\n", lResult);

 }/*local variables*/

 {
  long nCallbackResult;
  int someNum;
  someNum = 4;

  CSharpDLLPtr->CalltheCallbackFun((long)CCallbackFunction, (long)&someNum, 
   &nCallbackResult);

  wprintf(L"The nCallbackResult is %d\n", nCallbackResult);
 }/*local variables*/

 // Uninitialize COM.
 CoUninitialize();
 return 0;
}



And Set the character Set as Unicode :



四. Link C# library with C++ main :

 Include CSharpLibrary project into the solution, CCallCCsharp. And set the dependence:




 Now you could press the "play" button to run the code.

The result :


ArraySum
The result is 0
before call callbackFun ptr=4264216
牛羊豬雞貓狗
4
The nCallbackResult is 0


五. Key to reach the linking between C# and C++:


Under the Debug/Release folder, there is a file csharplibrary.tlh generated by C++ compiler, which is as the header of C# methods for C++:


// Created by Microsoft (R) C/C++ Compiler Version 14.00.50727.762 (92c42779).
//
// c:\users\gaiger\desktop\ccallccsharp\debug\csharplibrary.tlh
//
// C++ source equivalent of Win32 type library CSharpLibrary.tlb
// compiler-generated file created 05/08/16 at 01:10:19 - DO NOT EDIT!

#pragma once
#pragma pack(push, 8)

#include <comdef.h>

namespace CSharpLibrary {

//
// Forward references and typedefs
//

struct __declspec(uuid("406b133d-a839-43fe-882c-d126830250b2"))
/* LIBID */ __CSharpLibrary;
struct __declspec(uuid("ce8f3e90-5777-330d-9d1b-73e8fb398f2a"))
/* dual interface */ ManagedInterface;
struct /* coclass */ ManagedCSharpClass;
struct __declspec(uuid("b00d90a9-5151-380e-aae8-de1ba2b632e5"))
/* dual interface */ _ManagedCSharpClass;

//
// Smart pointer typedef declarations
//

_COM_SMARTPTR_TYPEDEF(ManagedInterface, __uuidof(ManagedInterface));
_COM_SMARTPTR_TYPEDEF(_ManagedCSharpClass, __uuidof(_ManagedCSharpClass));

//
// Type library items
//

struct __declspec(uuid("ce8f3e90-5777-330d-9d1b-73e8fb398f2a"))
ManagedInterface : IDispatch
{
    //
    // Raw methods provided by interface
    //

      virtual HRESULT __stdcall ArraySum (
        /*[in]*/ long pIntArray,
        /*[in]*/ long len,
        /*[in,out]*/ long * sum,
        /*[out,retval]*/ long * pRetVal ) = 0;
      virtual HRESULT __stdcall CalltheCallbackFun (
        /*[in]*/ long callbackFnPtr,
        /*[in]*/ long pArgs,
        /*[out,retval]*/ long * pRetVal ) = 0;
};

struct __declspec(uuid("585bb79e-7d64-3d56-ab11-70c238098a18"))
ManagedCSharpClass;
    // [ default ] interface _ManagedCSharpClass
    // interface _Object
    // interface ManagedInterface

struct __declspec(uuid("b00d90a9-5151-380e-aae8-de1ba2b632e5"))
_ManagedCSharpClass : IDispatch
{};

} // namespace CSharpLibrary

#pragma pack(pop)

The function declare is very explicit.

Note that there is namespace and inheritance syntax, therefore,  It is impossible  to  link C with C# directly.

 You could find that,: In C#, there is type IntPtr ; in the C++ part, it is long type. One could comprehend IntPtr as void*, the generic pointer.  So  it is very nature  to pass a pointer   as long integer (in 32 bit, long is length of 4 bytes, and VC++ of VS2005 for 32 bit only)

 The most key of the line :


NativeDelegateType callback =
                        Marshal.GetDelegateForFunctionPointer(callbackFnPtr,
                                                 typeof(NativeDelegateType))
                        as NativeDelegateType;

It is to cast the function pointer from C++  as C# delegate method.

Warning : the line:


using namespace CSharpLibrary;

should be the same as C# library name(actually, it should be the same as the tlb file name).