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).
沒有留言:
張貼留言