2

I have a native C++ application and I have a C# DLL. My native C++ application needs to access the functionality of the C# DLL. In order to do this, I have created a mixed-mode C++ DLL. My design is basically the same as this. In other words:

I have a C# DLL used by a Mixed C++ DLL, which is used by a native C++ application. Here's a simplified version of what I'm doing:

C# DLL

using System;

namespace CSDLL
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public Person() : this("Bob", 30)
        {
        }

        public Person(String name, Int32 age)
        {
            Name = name;
            Age = age;
        }

        public override string ToString()
        {
            return String.Format("<Person Name=\"{0}\" Age=\"{1}\"/>", Name, Age);
        }
    }
}

C++ DLL

Person.h: A header file suitable for native applications.

#include <string>

namespace CPPDLL
{
    class __declspec(dllexport) Person
    {
    public:
        Person () ;
        Person (const std::string &name, int age);
        void PrintPerson () const ;
    };
}

Person.cpp: Mixed code implementation. Notice the static variable person.

#include "Person.h"

namespace CPPDLL
{
    ref class Wrap 
    {
    public: 
        static CSDLL::Person ^person = gcnew CSDLL::Person () ;
    };

    Person::Person ()
    {
    }

    Person::Person (const std::string &name, int age)
    {
        System::String ^csName = gcnew System::String (name.data ()) ;
        Wrap::person = gcnew CSDLL::Person (csName, age) ;
    }

    void Person::PrintPerson () const
    {
        System::Console::WriteLine (Wrap::person) ;
    }
}

Sample Native C++ Application

#include "Person.h"

int main ()
{
    CPPDLL::Person person ("Billy", 5) ;
    person.PrintPerson () ;

    return 0 ;
}

Problem

  1. I have to expose a native API header from the C++ DLL to the C++ application.
  2. A native class cannot have a managed object as a member variable.
  3. Currently, if the native application created multiple instances of CPPDLL::Person, then they will all be working on the same instance of CSDLL::Person^. Because a native class cannot have a managed data member, I'm not sure of any obvious way of getting around this.

Question

Is there an easy/obvious way to make it so that every instance of CPPDLL::Person works with its own copy of CSDLL::Person^?

jliv902
  • 1,648
  • 1
  • 12
  • 21

1 Answers1

1

The question is to create a C# CSDLL::Person instance for each CPPDLL::Person instance in the mixed C++. As the CPPDLL::Person in a native class, it can not hold the reference of the managed object directly, but you can do it via GCHandle, it provides a way to access a managed object from unmanaged memory.

You can change code like this:

In the declaration of CPPDLL::Person, you add an integer member variable, this variable serves as a pointer to the managed object.

    namespace CPPDLL
{
    class __declspec(dllexport) Person
    {
    private:
        int m_personPtr;
    public:
        Person () ;
        Person (const std::string &name, int age);
        void PrintPerson () const ;
    };
}

The implementation of CPPDLL::Persion is below:

ref class Wrap 
    {
    public: 
        static int CreatePerson(const std::string &name, int age)
        {
            System::String ^csName = gcnew System::String (name.data ()) ;
            CSDLL::Person ^person = gcnew CSDLL::Person (csName, age) ;

            //pin the manage object and return the handle as int values
            GCHandle^ handle = GCHandle::Alloc(person, GCHandleType.Pinned);
            IntPtr^ ptr = handle->AddrOfPinnedObject();
            return ptr->ToInt32();
        }

        static void Release(int p)
        {
            //unpin the manage object and release it.
            GCHandle^ handle = GCHandle::FromIntPtr(gcnew IntPtr(p));
            if (handle != nullptr)
                handle->Free();
        }
    };

    Person::Person ()
        :m_personPtr(0)
    {
    }

    Person::Person (const std::string &name, int age)
    {
        m_personPtr = Wrap::CreatePerson(name, age);
    }

    Person::~Person ()      
    {
        Wrap::Release(m_personPtr);
    }

    void Person::PrintPerson () const
    {
        System::Console::WriteLine (Wrap::person) ;
    }
Matt
  • 6,010
  • 25
  • 36
  • Wow, this looks great. Just a couple of questions. Inside `CreatePerson()`, is `Alloc(obj` supposed to be `Alloc(person` instead? All I have to do to cast the handle to a `CSDLL::Person` object is to call `GetType()`, right? – jliv902 Nov 14 '14 at 20:25
  • @jliv902, yes, it should be the person, you don't need cast – Matt Nov 14 '14 at 20:33
  • I just realized that this solution does not work. I get an error saying, *"Object contains non-primitive or non-blittable data."* when I try this line: `GCHandle^ handle = GCHandle::Alloc(person, GCHandleType.Pinned);`. I need to read up on this problem. – jliv902 Nov 14 '14 at 22:51
  • @jliv902 add [StructLayout(LayoutKind.Sequential)] to the definition of the C# Person class – Matt Nov 15 '14 at 02:55
  • @jliv902 this blog gives more details on this:http://manski.net/2012/06/pinvoke-tutorial-pinning-part-4/ – Matt Nov 15 '14 at 02:58