Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass IntPtr to method from unmanaged C++ CLR hosting code?

Tags:

c++

c#

.net

I am using this tutorial as base for my code in 32bit unmanaged DLL https://code.msdn.microsoft.com/CppHostCLR-e6581ee0

Let's say I want to call TestIntPtr

 public class IntPtrTester
    {
        public static void TestIntPtr(IntPtr p)
        {
             MessageBox.Show("TestIntPtr Method was Called");
        } 
        public static void TestInt(int p)
        {
             MessageBox.Show("TestInt Method was Called");
        }
    }

How can I pass IntPtr parameter if on C++ side it represents handle? TestInt works, but for TestIntPtr I get the error that the method is not found. This is because the type of parameter is wrong.

In code from tutorial for TestInt I use

// HDC dc;
// The static method in the .NET class to invoke. 
bstr_t bstrStaticMethodName(L"TestInt"); 
SAFEARRAY *psaStaticMethodArgs = NULL; 
variant_t vtIntArg((INT) dc); 
variant_t vtLengthRet; 
...
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1); 
LONG index = 0; 
hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtIntArg); 
if (FAILED(hr)) 
{ 
    wprintf(L"SafeArrayPutElement failed w/hr 0x%08lx\n", hr); 
    goto Cleanup; 
} 

The question is what is correct code for TestIntPtr

// The static method in the .NET class to invoke. 
// HDC dc;
bstr_t bstrStaticMethodName(L"TestIntPtr"); 
SAFEARRAY *psaStaticMethodArgs = NULL; 
variant_t vtIntArg((INT) dc); // what do I have to write here?
variant_t vtLengthRet; 

I have tried:

variant_t vtIntArg((INT) dc); 
variant_t vtIntArg((UINT) dc); 
variant_t vtIntArg((long) dc); 
variant_t vtIntArg((UINT32) dc);
variant_t vtIntArg((INT32) dc); 

Maybe CLR expects IUNKNOWN of IntPtr there? But how to construct such instance? I have tried to call IntPtr constructor with this API but it returns variant of type V_INTEGER, so this is closed loop.

I know I can expose C# library using COM and how to use DllExports hack, I also can change C# part to accept just int or uint. But all these ways are not related to the question.

Currently it works for me with following C# helper

 public class Helper
 {
        public static void help(int hdc)
        {
             IntPtrTester.TestIntPtr(new IntPtr(hdc));
        }
 }

and

 variant_t vtIntArg((INT32) dc);

in c++. But this is ugly because I need this helper for the library I cannot influence.

like image 357
xdenser Avatar asked Nov 07 '16 10:11

xdenser


1 Answers

The list of Automation compatible type is documented here: 2.2.49.3 Automation-Compatible Types

As you see, there isn't any concept of a "pointer", handle, or anything that smells "native" (low level). This is because Automation was meant originally for VB (not the .NET one, VB/VBA/VBScript, etc.) that was a language and IDE designed for ease of use, not pointer handling fun, in a time when 64-bit Windows did not existed yet.

So, IntPtr, a raw and opaque pointer (not a handle) which has the particularity to be variable in storage size depending on the executing process bitness, is not a COM automation compatible type, so it can't be put as is, as a pointer, in a VARIANT, because in a VARIANT you want to use in interop code, you can only put automation compatible things.

There are many solutions/workarounds however, because VARIANT can transport 64 bits size things, if you ask nicely. So, you could define the method like this:

public static void Test(object input)
{
    // check for int (Int32) or long (Int64) here
}

And in C++ code do something like this:

variant_t vtIntArg;
if (64-bit mode)
{
    vtIntArg = (__int64)dc; // force VT_I8, this overload available only if _WIN32_WINNT >= 0x0501
}
else
{
    vtIntArg = (long)dc; // force VT_I4
}

Another solution is to define this in C#

public static void Test32(int ptr)
{
}

public static void Test64(long ptr)
{
}

And call the proper function, still using the __int64 overload for the Test64 method.

like image 121
Simon Mourier Avatar answered Oct 19 '22 20:10

Simon Mourier