How to expose C function pointer to C# in an embedded .Net CLR ?

Started by
5 comments, last by turnpast 7 years, 11 months ago
In a C++ project I'm hosting a .Net CLR and load an assembly with an HelloWorld function with the following code (compiled as 64 bits) :


struct _FooInterface : IUnknown
{
	virtual
		HRESULT __stdcall HelloWorld(LPWSTR name, LPWSTR* result, void* ptr) = 0;
};

extern "C"
{
	void __cdecl test_func(int);
}

void main()
{

		Microsoft::WRL::ComPtr<ICLRMetaHost> pMetaHost;
		Microsoft::WRL::ComPtr<ICLRRuntimeInfo> pRuntimeInfo;
		HRESULT hr;
		CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
		hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(pMetaHost.GetAddressOf()));
		hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(pRuntimeInfo.GetAddressOf()));
		hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(pRuntimeHost.GetAddressOf()));

		SampleHostControl* hostControl = new SampleHostControl();
		hr = pRuntimeHost->SetHostControl((IHostControl *)hostControl);

		ICLRControl* pCLRControl = nullptr;
		hr = pRuntimeHost->GetCLRControl(&pCLRControl);
		LPCWSTR assemblyName = L"mesh_managed";
		LPCWSTR appDomainManagerTypename = L"mesh_managed.CustomAppDomainManager";
		hr = pCLRControl->SetAppDomainManagerType(assemblyName, appDomainManagerTypename);

		hr = pRuntimeHost->Start();

		LPWSTR text;
		_FooInterface* appDomainManager = hostControl->GetFooInterface();
		hr = appDomainManager->HelloWorld(L"Player One", &text, &test_func);
		hr = pRuntimeHost->Stop();
}

For reference the code of host control class is :
class SampleHostControl : IHostControl
{
public:
	SampleHostControl()
	{
		m_refCount = 0;
		m_defaultDomainManager = NULL;
	}

	virtual ~SampleHostControl()
	{
		if (m_defaultDomainManager != NULL)
		{
			m_defaultDomainManager->Release();
		}
	}

	HRESULT __stdcall SampleHostControl::GetHostManager(REFIID id, void **ppHostManager)
	{
		*ppHostManager = NULL;
		return E_NOINTERFACE;
	}

	HRESULT __stdcall SampleHostControl::SetAppDomainManager(DWORD dwAppDomainID, IUnknown *pUnkAppDomainManager)
	{
		HRESULT hr = S_OK;
		hr = pUnkAppDomainManager->QueryInterface(__uuidof(_FooInterface), (PVOID*)&m_defaultDomainManager);
		return hr;
	}

	_FooInterface* GetFooInterface()
	{
		if (m_defaultDomainManager)
		{
			m_defaultDomainManager->AddRef();
		}
		return m_defaultDomainManager;
	}

	HRESULT __stdcall QueryInterface(const IID &iid, void **ppv)
	{
		if (!ppv) return E_POINTER;
		*ppv = this;
		AddRef();
		return S_OK;
	}

	ULONG __stdcall AddRef()
	{
		return InterlockedIncrement(&m_refCount);
	}

	ULONG __stdcall Release()
	{
		if (InterlockedDecrement(&m_refCount) == 0)
		{
			delete this;
			return 0;
		}
		return m_refCount;
	}

private:
	long m_refCount;
	_FooInterface* m_defaultDomainManager;
};
(took it from a blog post)

In my C# code I declare the following interface that mirror the _FooInterface one :
    [ComImport, Guid("A15DDC0D-53EF-4776-8DA2-E87399C6654D"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface Interface1
    {
        [return: MarshalAs(UnmanagedType.LPWStr)]
        string HelloWorld([MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr test_function);
    }
Then I'm implementing the appDomain and the helloWorld function as :
namespace mesh_managed
{
    public sealed class CustomAppDomainManager : AppDomainManager, Interface1
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        unsafe public delegate void test_function_ptr(int i);

        public CustomAppDomainManager()
        {

        }

        public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
        {
            this.InitializationFlags = AppDomainManagerInitializationOptions.RegisterWithHost;
        }

        [STAThread]
        public string HelloWorld(string name, IntPtr ptr)
        {
            var test_function = Marshal.GetDelegateForFunctionPointer<test_function_ptr>(ptr);
            test_function(0);
            return "Hello " + name;
        }
    }
}
(it's just a test function)

Unfortunatly the test_function(0) call doesn't work here in the assembly : an exception "An unhandled exception of type 'System.AccessViolationException' occurred in mesh_managed.dll" is thrown. If I remove the test_function(0) call everything seem to work as expected, the string is correctly return in the C++ side.

What am I doing wrong ? I'd like to expose an API to the CLR so that I can manipulate data with C#. There is no dll so I can't use DLLImport feature and I prefer to have C++ embed C# than the opposite.

Regards, Vincent.
Advertisement

Can you clarify what exactly you are trying to do?

If you are trying to make calls from c/c++ into a C# assembly (dll) then I believe that the preferred way is to create an adapter using C++/CLI as you should be able to both expose a c or c++ api and make calls directly.

If you are trying to make calls from C# to a C api than the prefered way is probably pinvoke. If you have a c++ api then C++/CLI wrapper.

Both pinvoke and C++/CLI should allow passing of function pointers if needed (with some legwork anyway) .

All the COM shenanagins is a bit over my head, is using COM a requirement for this project? I know that you can import com in a c# project (see: tlbimp.exe) and I belive that you can export one as well but I generally avoid COM unless it is absolutely required.

Hope this helps.

I'm trying to use the CLR as a script engine in my C++ code. I'm hosting a CLR instance which works as expected.
However I'd like to expose native functions to the CLR so that script code can have an interface to my C++ data.
Issue with PInvoke is that there is no DLL, my application is a standalone executable. As far as I know PInvoke only works on dll provided function
(Mono seems to have an DllImport("__internal") which is able to fetch dll in the process address space but I didn't test it).

I can split my app in an exe and a .dll but wouldn't that means the dll will be loaded twice ?

Sorry, CLR hosting is, unfortunately, not somehitng I know much about. It does strike me as something fairly advanced and quite possibly overkill (unless you really need a customized CLR). If you just want to load and run .NET assemblies or code consider using C++/CLI (see: the '/clr' flag) either on your main applicaton or in a secondary dll (if your main application must be completely unmanaged). You should be able to do everything you need (app domains, dymnamic loading, dynamic compilation etc.) to support C# scripting without a customized CLR.

I'm trying to use the CLR as a script engine in my C++ code. I'm hosting a CLR instance which works as expected.
However I'd like to expose native functions to the CLR so that script code can have an interface to my C++ data.
Issue with PInvoke is that there is no DLL, my application is a standalone executable. As far as I know PInvoke only works on dll provided function
(Mono seems to have an DllImport("__internal") which is able to fetch dll in the process address space but I didn't test it).

I can split my app in an exe and a .dll but wouldn't that means the dll will be loaded twice ?

"I can split my app in an exe and a .dll but wouldn't that means the dll will be loaded twice ?"

- Not necesserally, what kind of os are you working on?(I'll assume windows) Dlls are usually shared between executables.

Have you tried compiling it into a .lib file? If not maybe this can help you:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/299da822-5539-4e5b-9ba7-b614e564c9f4/presenting-a-c-library-lib-for-use-in-c-project?forum=vcgeneral

I would rather stay away from c++/cli since it looks like it's being deprecated by MS and it's not portable. While the clr is currently on Windows only there is mono and net core for Linux and other platforms.

Yeah it used to be Managed C++ then C++/CLI and now I guess it is C++/CX leave it to MS...

Anyway, if portability is a required feature then the whole problem becomes somewhat more difficult. The code you posted looks to be using a bunch of MS specific COM APIs and I strongly suspect that making it cross platform is simply not in the cards.

Looks like you can embed mono in C though. Perhaps that is the path you should be pursuing.

This topic is closed to new replies.

Advertisement