4

I have a static function in my C program which address is transmitted to a C# .NET program. The address is correct, but is it possible to call this function within C#?

Have some Code:

static void test_callback() 
{
    printf("test_callback called\n");
}

void callCSharpFunction ()
{
    HRESULT status;
    BOOL Started;
    DWORD result;
    char ptr[5];
    int p1;
    Started = FALSE;    

    status = CorBindToRuntimeEx(
                 NULL,
                 NULL,
                 0,
                 &CLSID_CLRRuntimeHost,
                 &IID_ICLRRuntimeHost,
                 (PVOID *)&Host
                 );

    if (FAILED(status)) {}

    status = ICLRRuntimeHost_Start(Host);

    if (FAILED(status)) {}

    Started = TRUE;

    p1 = (int)(&test_callback);
    ptr[0] = 0xFF & ((int)&test_callback >> 0);
    ptr[1] = 0xFF & ((int)&test_callback >> 8);
    ptr[2] = 0xFF & ((int)&test_callback >> 16);
    ptr[3] = 0xFF & ((int)&test_callback >> 24);

    printf("test_callback is at 0x%X\n", (int)&test_callback);

    status = ICLRRuntimeHost_ExecuteInDefaultAppDomain(
                 Host,
                 L"C:\\pathtodll\\mydll.dll",
                 L"myclass",
                 L"myfunction",
                 (LPCWSTR)ptr,
                 &result
                 );

    printf("Result is 0x%X\n", result);
}

And in my C# .NET project I have

public static unsafe void callCallback(int ptr)
{
    void (*callback)();

    // I wish I could do that but "unsafe" seems not to allow function pointers ..
    callback = (void(*)())ptr; 

    callback();
}

public static int test(string param)
{
    char[] ptrChar = param.ToCharArray();
    int ptrInt = 0;

    ptrInt = ( ((int)(0xFF00 & (int)ptrChar[1]) | (0x00FF & (int)ptrChar[1])) << 16 ) |
                (int)(0xFF00 & (int)ptrChar[0]) | (0x00FF & (int)ptrChar[0]);

    callCallback(ptrInt);
}

1 Answer 1

9

You'll want to use Marshal.GetDelegateForFunctionPointer. You won't even need to use unsafe code.

delegate void TestCallbackDelegate(); //must match the signature of test_callback()

public static void callCallback(int ptr)
{
    IntPtr nativePtr = new IntPtr( ptr );

    var callback = Marshal.GetDelegateForFunctionPointer<TestCallbackDelegate>( nativePtr );

    callback();
}

public static int test(string param)
{
    char[] ptrChar = param.ToCharArray();
    int ptrInt = 0;

    ptrInt = ( ((int)(0xFF00 & (int)ptrChar[1]) | (0x00FF & (int)ptrChar[1])) << 16 ) |
                (int)(0xFF00 & (int)ptrChar[0]) | (0x00FF & (int)ptrChar[0]);

    callCallback(ptrInt);
}

Although a much simpler way would be to just pass a void* to the C# method, and it will automatically get marshalled to IntPtr. Here's a minimal example of that:

C++

//invoke.cpp
//compile with: cl /EHsc /LD /nologo invoke.cpp
#include <stdio.h>

static void test_callback() 
{
    printf("test_callback called\n");
}

extern "C" __declspec( dllexport ) void* getPointer()
{
    return (void*)&test_callback; //Return a raw pointer to the test_callback function.
}

C#

//invoke.cs
//compile with: csc /nologo invoke.cs
using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport( "invoke.dll" )]
    private static extern IntPtr getPointer();

    private delegate void TestCallbackDelegate(); //Delegate that matches the signature of test_callback

    static void main()
    {
        IntPtr ptr = getPointer(); //Fetch the native void pointer.
        TestCallbackDelegate test_callback = Marshal.GetDelegateForFunctionPointer<TestCallbackDelegate>( ptr ); //Marshal the void pointer to a delegate.
        test_callback(); //Invoke the native C function.
    }
}

I used the DllImport attribute to avoid having to invoke the CLR as you're doing, but it's the same idea.


EDIT: Because I realized the above doesn't apply to what the OP was asking, I'll include an updated and proper sample. The above will remain for posterity.

C

#define COBJMACROS

#include <stdio.h>
#include <windows.h>
#include <mscoree.h>

static void test_callback()
{
    printf( "test_callback has been called.\n" );
}

int main( void )
{
    HRESULT status;
    ICLRRuntimeHost *Host;
    BOOL Started;
    DWORD Result;

    Host = NULL;
    Started = FALSE;

    status = CorBindToRuntimeEx( NULL, NULL, 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (void**)&Host );

    if( FAILED( status ) )
        goto cleanup;

    status = ICLRRuntimeHost_Start( Host );

    if( FAILED( status ) )
        goto cleanup;

    Started = TRUE;

    int ptr = (int)&test_callback;
    printf( "test_callback is at 0x%X\n", ptr );

    char param[5];
    param[0] = 0xFF & ( ptr >> 0 );
    param[1] = 0xFF & ( ptr >> 8 );
    param[2] = 0xFF & ( ptr >> 16 );
    param[3] = 0xFF & ( ptr >> 24 );
    param[4] = '\0';

    status = ICLRRuntimeHost_ExecuteInDefaultAppDomain( Host, L"invoke.dll", L"InteropTesting.Invoker", L"InvokeCallback", (LPCWSTR)param, &Result );

    if( FAILED( status ) )
        goto cleanup;

cleanup:
    if( Started )
        ICLRRuntimeHost_Stop( Host );

    if( Host != NULL )
        ICLRRuntimeHost_Release( Host );

    return SUCCEEDED( status ) ? 0 : 1;
}

C#

using System;
using System.Runtime.InteropServices;

namespace InteropTesting
{
    public static class Invoker
    {
        private delegate void TestCallbackDelegate();

        public static int InvokeCallback( string param )
        {
            //C# has a built-in means of turning byte arrays into integers
            //so we'll use BitConverter instead of using the bitwise operators.
            char[] chars = param.ToCharArray();
            int ptr = BitConverter.ToInt32( Array.ConvertAll( chars, c => (byte)c ), 0 );

            var test_callback = (TestCallbackDelegate)Marshal.GetDelegateForFunctionPointer( new IntPtr( ptr ), typeof( TestCallbackDelegate ) );
            test_callback();

            return 0;
        }
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Well thank you and +1 for the good explanation but unfortunately what I am doing here is using a dll that is written in C# .NET in a C program. So the problem case is actually the reverse.
@StefanFalk It seems my answer was a little premature, as I failed to realize that when hosting the CLR, the target method must have the signature int method( string param ). I've updated my answer with a proper sample to do what you asked originally.
It is 03:33 am here that is why I won't try this out right now but defenitely tomorrow. Anyway, I am quite sure that this works. Thanks for this answer!
You are using BitConverter.ToInt32 but pointers are 64bits on x64

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.