标准c类库,.net 调用C++类库

其实这一直是个很无解的问题。最好的办法是将C++类库写成COM。
但是有时候往往不能这个做。那就只有两种办法:转成C函数形式或者Manage C++封装。
下文就介绍了这两种方法。
原帖:http://www.codeproject.com/KB/cs/marshalCPPclass.aspx

Introduction

I recently needed to marshal some legacy C++ classes into a C# project _disibledevent=>Audience This article assumes the reader is knowledgeable in C# and .NET and is already familiar with PInvoke and marshaling.

Article

I had existing (unmanaged) C++ DLLs which needed to be used with a managed C# project I was working _disibledevent=>Upon first investigating this issue, I was hoping to be able to declare a definition for a C# version of the class and marshal the object back and forth between the managed and unmanaged memory spaces, similar to how a structure is marshaled back and forth. Unfortunately, this is not possible; in my research I discovered that unmanaged C++ classes can't be marshaled and that the best approach is to either create bridge/wrapper C-functions for the public methods of the class and marshal the functions, or to create a bridge DLL in managed C++.

Solution A: Create Bridge Functions in C and Use PInvoke

Suppose we have the following unmanaged C++ class:
标准c类库,.net 调用C++类库 Collapse
class EXAMPLEUNMANAGEDDLL_API CUnmanagedTestClass { public: CUnmanagedTestClass(); virtual ~CUnmanagedTestClass(); void PassInt(int nValue); void PassString(char* pchValue); char* ReturnString(); };
Running dumpbin _disibledevent=>Screenshot标准c类库,.net 调用C++类库
(Click for larger view. We'll cover the results from dumpbin in a moment).
Since the instantiation of a C++ class object is just a pointer, we can use C#'s IntPtr data type to pass unmanaged C++ objects back and forth, but C-functions need to be added to the unmanaged DLL in order to create and dispose instantiations of the class:
标准c类库,.net 调用C++类库 Collapse
// C++: extern "C" EXAMPLEUNMANAGEDDLL_API CUnmanagedTestClass* CreateTestClass() { return new CUnmanagedTestClass(); } extern "C" EXAMPLEUNMANAGEDDLL_API void DisposeTestClass( CUnmanagedTestClass* pObject) { if(pObject != NULL) { delete pObject; pObject = NULL; } }
标准c类库,.net 调用C++类库 Collapse
// C#: [DllImport("ExampleUnmanagedDLL.dll")] static public extern IntPtr CreateTestClass(); [DllImport("ExampleUnmanagedDLL.dll")] static public extern void DisposeTestClass(IntPtr pTestClassObject); IntPtr pTestClass = CreateTestClass(); DisposeTestClass(pTestClass); pTestClass = IntPtr.Zero; // Always NULL out deleted objects in order to prevent a dirty pointer
This allows us to pass the object back and forth, but how do we call the methods of our class? There are two approaches to accessing the methods. The first approach is to use PInvoke and to use CallingConvention.ThisCall. If you go back to the output from dumpbin, you will see the mangled name for the PassInt() method is "?PassInt@CUnmanagedTestClass@@QAEXH@Z". Using CallingConvention.ThisCall, the PInvoke definition of PassInt() is:
标准c类库,.net 调用C++类库 Collapse
[DllImport("ExampleUnmanagedDLL.dll", EntryPoint="?PassInt@CUnmanagedTestClass@@QAEXH@Z", CallingConvention=CallingConvention.ThisCall)] static public extern void PassInt(IntPtr pClassObject, int nValue);
The second approach is to create C-functions which act as a bridge for each public method within the DLL...
标准c类库,.net 调用C++类库 Collapse
// C++: extern "C" EXAMPLEUNMANAGEDDLL_API void CallPassInt( CUnmanagedTestClass* pObject, int nValue) { if(pObject != NULL) { pObject->PassInt(nValue); } } . . .
...and marshal each of new C-functions in C#...
标准c类库,.net 调用C++类库 Collapse
// C#: [DllImport("ExampleUnmanagedDLL.dll")] static public extern void CallPassInt(IntPtr pTestClassObject, int nValue); . . .
I chose to go with the second approach; the name mangling the compiler does means that the first approach is susceptible to breaking if a different compiler is used to compile the C++ DLL (newer version, different vendor, etc...), or if additional methods are added to the class. There is a little extra work involved with the second approach, but I feel the extra work is rewarded by having better maintainable code and code which is less likely to break in the future.
At this point I should point out that I added the bridge functions to the original DLL and recompiled the DLL, but what if the DLL in question is a third party DLL and you don't have access to the sources so you can't recompile the DLL (you _disibledevent=>Creating a new DLL in unmanaged C and place the bridge functions within the new DLL.
  • Create a managed C++ DLL and have it act as the bridge between the C# code and the unmanaged C++ classes (see Solution B further _disibledevent=>At this point, the C# code to call our C++ class looks like:
    标准c类库,.net 调用C++类库 Collapse
    // C#: IntPtr pTestClass = CreateTestClass(); CallPassInt(pTestClass, 42); DisposeTestClass(pTestClass); pTestClass = IntPtr.Zero;
    This is fine as it is, but this isn't very Object-Oriented. Suppose you aren't the _disibledevent=>During my investigation, I came across the following newsgroup posting...
    http://groups.google.com/group/microsoft.public.dotnet.framework.interop/ browse_thread/thread/d4022eb907736cdd/0e74fa0d34947251?lnk=gst&q= C%2B%2B+class&rnum=6&hl=en#0e74fa0d34947251
    ...and I decided to mirror this approach and create a class in C# called CSUnmanagedTestClass:
    标准c类库,.net 调用C++类库 Collapse
    // C#: public class CSUnmanagedTestClass : IDisposable { #region PInvokes [DllImport("TestClassDLL.dll")] static private extern IntPtr CreateTestClass(); [DllImport("TestClassDLL.dll")] static private extern void DisposeTestClass(IntPtr pTestClassObject); [DllImport("TestClassDLL.dll")] static private extern void CallPassInt(IntPtr pTestClassObject, int nValue); . . . #endregion PInvokes #region Members private IntPtr m_pNativeObject; // Variable to hold the C++ class's this pointer #endregion Members public CSUnmanagedTestClass() { // We have to Create an instance of this class through an exported // function this.m_pNativeObject = CreateTestClass(); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool bDisposing) { if(this.m_pNativeObject != IntPtr.Zero) { // Call the DLL Export to dispose this class DisposeTestClass(this.m_pNativeObject); this.m_pNativeObject = IntPtr.Zero; } if(bDisposing) { // No need to call the finalizer since we've now cleaned // up the unmanaged memory GC.SuppressFinalize(this); } } // This finalizer is called when Garbage collection occurs, but _disibledevent=>Now, the C# client of this code simply does:
    标准c类库,.net 调用C++类库 Collapse
    // C#: CSUnmanagedTestClass testClass = new CSUnmanagedTestClass(); testClass.PassInt(42); testClass.Dispose();

    Solution B: Create a Bridge DLL in Managed C++

    Another option is to leave the original DLL untouched and create a new DLL in managed C++ to act as a bridge between the managed C# code and the unmanaged C++ classes in the unmanaged DLL. Using the CUnmanagedTestClass within the managed DLL wasn't difficult, and PInvoke definitions weren't required, but the managed C++ syntax and classes which needed to be used was a bit vexing:
    标准c类库,.net 调用C++类库 Collapse
    // MCPP: // Forward declariation class CUnmanagedTestClass; public ref class CExampleMCppBridge { public: CExampleMCppBridge(); virtual ~CExampleMCppBridge(); void PassInt(int nValue); void PassString(String^ strValue); String^ ReturnString(); private: CUnmanagedTestClass* m_pUnmanagedTestClass; }; CExampleMCppBridge::CExampleMCppBridge() : m_pUnmanagedTestClass(NULL) { this->m_pUnmanagedTestClass = new CUnmanagedTestClass(); } CExampleMCppBridge::~CExampleMCppBridge() { delete this->m_pUnmanagedTestClass; this->m_pUnmanagedTestClass = NULL; } void CExampleMCppBridge::PassInt(int nValue) { this->m_pUnmanagedTestClass->PassInt(nValue); } . . .
    标准c类库,.net 调用C++类库 Collapse
    // C#: CExampleMCppBridge example = new CExampleMCppBridge(); example.PassInt(42); example.Dispose();
    (and I have to admit, I'm not very fluent in MCPP)

    Pros/Cons

    Both approach A and approach B have their own pros and cons. Are you unfamiliar with MCPP? Go with approach A and create C-functions to wrap the public methods of the class and use PInvoke. Can't modify the original DLL and don't want to create PInvode definitions? Create bridge classes in a new MCPP DLL as demonstrated in approach B.

    Conclusion

    In this article I have presented the reader with a number of different approaches and solutions to the problem of marshaling an unmanaged C++ class to C#. For the sake of brevity I have _disibledevent=>标准c类库,.net 调用C++类库 Collapse
    CallPassString() CallReturnString()
    ...are in the source code accompanying this article.
    Tags:  c语言调用dos c语言函数调用 java调用c 标准c类库

    延伸阅读

    最新评论

    发表评论