zoukankan      html  css  js  c++  java
  • Marshaling between Managed and Unmanaged Code(转载)

    Marshaling between Managed and Unmanaged Code
    Yi Zhang and Xiaoying Guo

    Code download available at: CLRInsideOut2008_01.exe (1269 KB)
    Browse the Code Online
    Let's face it. The world is not perfect. Very few companies are developing entirely in managed code, and on top of that there's a lot of legacy unmanaged code out there you need to work with. How do you integrate your managed and unmanaged projects? Does it take the form of calling into unmanaged code from a managed application or calling into managed code from an unmanaged application?
    Fortunately, Microsoft® .NET Framework interop opens a channel between managed and unmanaged code, and marshaling plays a very important role in that connection in that it allows for data exchange between the two (see Figure 1). There are many factors that affect the way the CLR marshals data between the unmanaged and managed worlds, including attributes such as [MarshalAs], [StructLayout], [InAttribute], and [OutAttribute] as well as language keywords such as out and ref in C#.
    Figure 1 Bridging the Gap between Managed and Unmanaged Code (Click the image for a larger view)
    Because of the sheer number of these factors, it can be a challenge to marshal correctly, which requires you to understand many details in both unmanaged and managed code. In this column, we'll cover the basic yet confusing topics you'll encounter in your everyday marshaling attempts. We won't cover custom marshaling, marshaling complex structures, or other advanced topics, but with a solid understanding of the basics, you'll be ready to begin tackling those as well.

    [InAttribute] and [OutAttribute]
    The first marshaling topic we'll discuss concerns the use of InAttribute and OutAttribute, attribute types located in the System.Runtime.InteropServices namespace. (When applying these attributes in your code, C# and Visual Basic® allow you to use the abbreviated forms [In] and [Out], but we will stick to the full names to avoid confusion.)
    When applying to method parameters and return values, these attributes control marshaling direction, so they're known as directional attributes. [InAttribute] tells the CLR to marshal data from the caller to the callee at the beginning of the call, while [OutAttribute] tells the CLR to marshal back from the callee to the caller upon return. Both the caller and the callee can be either unmanaged or managed code. For example, in a P/Invoke call, managed code is calling unmanaged code. However, in a reverse P/Invoke, unmanaged code could call managed code through a function pointer.
    There are four possible combinations in which [InAttribute] and [OutAttribute] can be used: [InAttribute] only, [OutAttribute] only, [InAttribute, OutAttribute], and neither. When neither attribute is specified, you are telling the CLR to figure out directional attributes by itself, with [InAttribute] usually applied by default. However, in the case of the StringBuilder class, both [InAttribute] and [OutAttribute] are applied when neither attribute is specified. (See the later section on StringBuilder for details.) Also, the use of out and ref keywords in C# could change the attributes that are applied, as shown in Figure 2. Note that if no keyword is specified for a parameter, it means that it is an input parameter by default.

    C# Keyword Attribute
    (none specified) [InAttribute]
    out [OutAttribute]
    ref [InAttribute], [OutAttribute]
    VB.NET Keyword Attribute
    ByVal: <InAttribtue>
    ByRef: <InAttribute>,<OutAttribute>
    Consider the code in Figure 3. There are three native C++ functions, and they are all making the same change to arg. Also, note that the use of strcpy for string manipulation is for illustrative purposes only—production code should instead use the safe versions of these functions, which can be found at msdn.microsoft.com/msdnmag/issues/05/05/SafeCandC.
    MARSHALLIB_API void __stdcall Func_In_Attribute(char *arg)
    {
        printf("Inside Func_In_Attribute: arg = %s\n", arg);
        strcpy(arg, "New");
    }
    
    MARSHALLIB_API void __stdcall Func_Out_Attribute(char *arg)
    {
        printf("Inside Func_Out_Attribute: arg = %s\n", arg);
        strcpy(arg, "New");
    }
    
    MARSHALLIB_API void __stdcall Func_InOut_Attribute(char *arg)
    {
        printf("Inside Func_InOut_Attribute: arg = %s\n", arg);
        strcpy(arg, "New");
    }
    
    
    The only thing that is different is how we will invoke them, using directional attributes in the P/Invoke signatures, as shown in the following C# code:
    [DllImport(@"MarshalLib.dll")]
    public static extern void Func_In_Attribute([In]char[] arg);
    [DllImport(@"MarshalLib.dll")]
    public static extern void Func_Out_Attribute([Out]char[] arg);
    [DllImport(@"MarshalLib.dll")]
    public static extern void Func_InOut_Attribute([In, Out]char[] arg);
    
        <DllImport("MarshalLib.dll")> _
        Public Sub Func_In_Attribute(<[In]()> ByVal arg As Char())
    
        End Sub
    
        <DllImport("MarshalLib.dll")> _
        Public Sub Func_Out_Attribute(<Out()> ByVal arg As Char())
    
        End Sub
    
        <DllImport("MarshalLib.dll")> _
        Public Sub Func_InOut_Attribute(<[In](), Out()> ByVal arg As Char())
    
        End Sub
    
    If you call these functions from managed code by P/Invoke and pass in "Old" as a char array to the functions, you'll get the following output (abridged for demonstration purposes):
    Before Func_In_Attribute: arg = Old
    Inside Func_In_Attribute: arg = Old
    After Func_In_Attribute: arg = Old
    
    Before Func_Out_Attribute: arg = Old
    Inside Func_Out_Attribute: arg =
    After Func_Out_Attribute: arg = New
    
    Before Func_InOut_Attribute: arg = Old
    Inside Func_InOut_Attribute: arg = Old
    After Func_InOut_Attribute: arg = New
    
    Let's look at the results more closely. In Func_In_Attribute, the original value is passed in, but the change made inside Func_In_Attribute is not propagated back. In Func_Out_Attribute, the original value is not passed in and the change made inside Func_Out_Attribute is propagated back. In Func_InOut_Attribute, the original value is passed in and the change made inside Func_Out_Attribute is propagated back. However, things could be totally different if you make a slight change. This time, let's change the native function to use Unicode, as shown below:
    MARSHALLIB_API void __stdcall Func_Out_Attribute_Unicode(wchar_t *arg)
    {
        wprintf(L"Inside Func_Out_Attribute_Unicode: arg = %s\n", arg);
        printf("Inside Func_Out_Attribute_Unicode: strcpy(arg, \"New\")\n");
        wcscpy(arg, L"New");
    }
    
    Here we declare the C# function, apply only [OutAttribute], and change CharSet to CharSet.Unicode:
    [DllImport(@"MarshalLib.dll", CharSet=CharSet.Unicode)]
    public static extern void Func_Out_Attribute_Unicode([Out]char[] arg);
    
        <DllImport("MarshalLib.dll", CharSet:=CharSet.Unicode)> _
        Public Sub Func_Out_Attribute_Unicode(<Out()> ByVal arg As Char())
    
        End Sub
    
    Here's the output:
    Before Func_Out_Attribute_Unicode: arg = Old
    Inside Func_Out_Attribute_Unicode: arg = Old
    After Func_Out_Attribute_Unicode: arg = New
    
    Interestingly, the original value is passed even though there is no [InAttribute]. The [DllImportAttribute] tells the CLR to marshal Unicode, and, because char type in the CLR is also Unicode, the CLR sees an opportunity to optimize the marshaling process by pinning the char array and passing the address of the char directly. (You'll see a detailed discussion of copying and pinning later.) However, this doesn't mean you should rely on this behavior. Instead, the correct marshaling directional attributes should always be used when not relying on the CLR's default marshaling behavior. A typical example of this default behavior is the case of an int argument; specifying [InAttribute] int arg isn't necessary.
    There are cases when [OutAttribute] will be ignored. For example, [OutAttribute]int doesn't make any sense, so the [OutAttribute] is simply ignored by the CLR. The same is true of [OutAttribute] string because string is immutable.
    Interface definition (IDL) files also have [in] and [out] attributes, which can be considered the same as [InAttribute] and [OutAttribute] in the CLR.

    Keywords Out and Ref and Passing by Reference
    Previously, we've shown that C# out and ref keywords can be directly mapped to [InAttribute] and [OutAttribute]. As a matter of fact, out and ref can also change what data type the CLR will marshal into or from. Passing data as out or ref is the same as passing by reference. If you examine the corresponding function signature in intermediate language (IL) using ILDASM, you can see there is an ampersand character (&) next to the type, which means that the argument should be passed by reference. When passing by reference, an extra level of indirection will be added by the CLR. Figure 4 shows some examples.

    C# Signature VB Signature Unmanaged Signature MSIL Signature Actual MSIL Signature Seen by the CLR
    Basic Types      
    int arg ByVal arg As Integer int arg int [in] int
    out int arg N/A int *arg [out] int & [out] int &
    ref int arg ByRef arg As Integer int *arg int & [in, out] int &
    Structs        
    MyStruct arg ByVal arg as MyStruct MyStruct arg MyStruct [in] MyStruct
    out MyStruct arg N/A MyStruct *arg [out] MyStruct & [out] MyStruct &
    ref MyStruct arg ByRef arg As MyStruct MyStruct *arg MyStruct & [in, out] MyStruct &
    Strings        
    string arg ByVal arg As String char *arg string [in] string
    out string arg N/A char **arg [out] string & [out] string &
    ref string arg ByRef arg As String char **arg string & [in, out] string &
    Classes        
    MyClass arg ByVal arg As MyClass MyClass *arg MyClass [in] MyClass
    out MyClass arg N/A MyClass **arg [out] MyClass & [out] MyClass &
    ref MyClass arg ByRef arg As MyClass MyClass **arg MyClass & [in, out] Myclass &
    Let's summarize what we discussed for out and ref in the table shown in Figure 5.

    C# Signature VB Signature MSIL Signature Default Direction Attributes
    <type> ByVal<type> type [InAttribute]
    out <type> N/A [OutAttribute] type & [OutAttribute]
    ref <type> ByRef<type> type & [InAttribute, OutAttribute]
    Please note that when passing by reference, if no directional attributes are specified, the CLR will apply [InAttribute] and [OutAttribute] automatically, which is why there is only "string &" in the Microsoft Intermediate Language (MSIL) signature in Figure 4. If any of these attributes are specified, the CLR will follow them instead of the default behavior, as shown in this example:
    public static extern void 
          PassPointerToComplexStructure(
            [In]ref ComplexStructure 
            pStructure);
    
        <DllImport("MarshalLib.dll")> _
        Public Sub PassPointerToComplexStructure(<[In]()> ByRef pstructure As ComplexStructure)
    
        End Sub
    
    The signature above will override the default directional behavior of ref, making it [InAttribute]-only. In this particular case, if you are doing a P/Invoke, a pointer to ComplexStructure (which is a value type) is being passed from the CLR side to the native side, but the callee can't make any changes visible to the ComplexStructure pointed to by pStructure pointer. Figure 6 shows other examples of directional attributes and keyword combinations.

         
    C# Signature Unmanaged IDL Signature MSIL Signature
    Out    
    [InAttribute] out int arg Compiler error CS0036. An out parameter cannot have the In attribute. N/A
    [OutAttribute] out int arg [out] int *arg [out] int &
    [InAttribute, OutAttribute] out int arg Compiler error CS0036. An out parameter cannot have the In attribute. N/A
    Ref    
    [InAttribute] ref int arg [in] int *arg [in] int &
    [OutAttribute] ref int arg Compiler error CS0662 cannot specify only Out attribute on a ref parameter. Use both In and Out attributes, or neither. N/A
    [InAttribute, OutAttribute] ref int arg [in, out] int *arg [in] [out] int &

    Return Values
    So far we have only been discussing arguments. What about values returned from functions? The CLR automatically treats a return value as if it is a normal argument using the [OutAttribute]. Also, the CLR can make the transformation to the function signature, a process controlled by the PreserveSigAttribute. If [PreserveSigAttribute] is set to false when applied to a P/Invoke signature, the CLR will map HRESULT return values to managed exceptions, and it will map [out, retval] parameters to the function's return value. So the following managed function signature
    public static string extern GetString(int id);
    
        Public Function GetString(ByVal id As Integer) As String
    
        End Function
    
    would become the unmanaged signature:
    HRESULT GetString([in]int id, [out, retval] char **pszString);
    
    If [PreserveSigAttribute] is set to true (the default for P/Invoke), this transformation won't happen. Note that with COM functions, [PreserveSigAttribute] is usually set to false by default, though there are a number of ways to change that. For details, please consult the MSDN® documentation for TlbExp.exe and TlbImp.exe.

    StringBuilder and Marshaling
    The CLR marshaler has built-in knowledge of the StringBuilder type and treats it differently from other types. By default, StringBuilder is passed as [InAttribute, OutAttribute]. StringBuilder is special because it has a Capacity property that can determine the size of the required buffer at run time, and it can be changed dynamically. Therefore, during the marshaling process, the CLR can pin StringBuilder, directly pass the address of internal buffer used in StringBuilder, and allow the contents of this buffer to be changed by native code in place.
    To take full advantage of StringBuilder, you'll need to follow all of these rules:
    1. Don't pass StringBuilder by reference (using out or ref). Otherwise, the CLR will expect the signature of this argument to be wchar_t ** instead of wchar_t *, and it won't be able to pin StringBuilder's internal buffer. Performance will be significantly degraded.
    2. Use StringBuilder when the unmanaged code is using Unicode. Otherwise, the CLR will have to make a copy of the string and convert it between Unicode and ANSI, thus degrading performance. Usually you should marshal StringBuilder as LPARRAY of Unicode characters or as LPWSTR.
    3. Always specify the capacity of StringBuilder in advance and make sure the capacity is big enough to hold the buffer. The best practice on the unmanaged code side is to accept the size of the string buffer as an argument to avoid buffer overruns. In COM, you can also use size_is in IDL to specify the size.

    Copying and Pinning
    When the CLR performs data marshaling, it has two options: copying and pinning (see msdn2.microsoft.com/23acw07k).
    By default, the CLR will make a copy that will be used during marshaling. For example, if managed code is passing a string as an ANSI C-String to unmanaged code, the CLR will make a copy of the string, convert it to ANSI, and then pass the temporary's pointer to unmanaged code. That copying can be quite slow and can create performance problems.
    In certain cases, the CLR is able to optimize the marshaling process by directly pinning the managed object in the Garbage Collector (GC) heap so that it cannot be relocated during the call. The pointer to the managed object (or to somewhere inside the managed object) will be passed directly to unmanaged code.
    Pinning is performed when all of the following conditions are met: First, managed code must be calling native code, not the other way around. Second, the type must be blittable or must be capable of becoming blittable under certain conditions. Third, you're not passing by reference (out or ref), and fourth, the caller and callee are in the same thread context or apartment.
    The second rule deserves further explanation. A blittable type is one that has a common representation in both managed and unmanaged memory. As such, blittable types do not require conversion when marshaling. A typical example of a type that is non-blittable, but can become blittable, is the char type. By default, it is non-blittable since it can be mapped to either Unicode or ANSI. However, because char is always Unicode in the CLR, it does become blittable when you specify [DllImportAttribute(CharSet= Unicode)] or [MarshalAsAttribute(UnmanagedType.LPWSTR)]. In the following example, arg can be pinned in PassUnicodeString, but it cannot be pinned in PassAnsiString:
    [DllImport(@"MarshalLib.dll", CharSet = CharSet.Unicode)]
    public static extern string PassUnicodeString(string arg);
    
    [DllImport(@"MarshalLib.dll", CharSet = CharSet.Ansi)]
    public static extern string PassAnsiString(string arg);
        <DllImport("MarshalLib.dll", CharSet:=CharSet.Unicode)> _
        Public Function PassUnicodeString(ByVal arg As String) As String
    
        End Function
    
        <DllImport("MarshalLib.dll", CharSet:=CharSet.Ansi)> _
        Public Function PassAnsiString(ByVal arg As String) As String
    
        End Function

    Memory Ownership
    During a function call, a function can make two kinds of changes to its arguments: a reference change or an in-place change. A reference change involves changing where a pointer points; if the pointer already points to a piece of allocated memory, that memory may first need to be freed before the pointer to it is lost. An in-place change involves changing the memory at the location pointed to by the reference.
    Which of these changes is made depends on the type of the argument and, most importantly, the contract between the callee and caller. However, since the CLR cannot figure out the contract automatically, it has to rely on common knowledge about types, as illustrated in Figure 7.

    IDL Signature Change Type
    [In] Type No change allowed
    [In] Type * No change allowed
    [Out] Type * In-place change
    [In, Out] Type * In-place change
    [In] Type ** No change allowed
    [Out] Type ** Reference change
    [In, Out] Type ** Reference change or in-place change
    As previously discussed, only reference types have two levels of indirection when passing by reference (there are a few exceptions, however, such as "[MarshalAs(UnmanagedType.LPStruct)]ref Guid"), so only the pointer or reference to a reference type can be changed, as shown in Figure 8.

    C# Signature VB Signature Change Type
    int arg arg As Integer No change allowed
    out int arg N/A In-place change
    ref int arg ByRef arg as Integer In-place change
    string arg ByVal arg as String No change allowed
    out string arg N/A Reference change
    ref string arg ByRef arg As String Reference change or in-place change
    [InAttribute, OutAttribute] StringBuilder arg <InAttribute,OutAttribute> arg As StringBuilder In-place change
    [OutAttribute] StringBuilder arg <OutAttribute> arg As StringBuilder In-place change
    You don't need to worry about memory ownership for an in-place change because the caller has allocated memory for the callee and the caller owns the memory. Let's take "[OutAttribute] StringBuilder" as an example here. The corresponding native type is char * (assuming ANSI), since we are not passing by reference. Data is marshaled out, not in. Memory is allocated by the caller (in this case the CLR). The size of the memory is determined by the capacity of the StringBuilder object. The callee doesn't need to concern itself with the memory.
    To change the string, the callee will make the change directly to the memory itself. However, when making a reference change, it is critical to recognize who owns which memory, otherwise any number of unexpected outcomes can occur. Regarding ownership issues, the CLR follows COM-style conventions:
    • Memory passed as [in] is owned by the caller and should be both allocated by the caller and freed by the caller. The callee should not try to free or modify that memory.
    • Memory allocated by the callee and passed as [out] or returned is owned by the caller and should be freed by the caller.
    • The callee can free memory passed as [in, out] from the caller, allocate new memory for it, and overwrite the old pointer value, thereby passing it out. The new memory is owned by the caller. This requires two levels of indirection, such as char **.
    In the interop world, caller/callee becomes CLR/native code. The rules above imply that in the unpinned case, if when in native code you receive a pointer to a block of memory passed to you as [out] from the CLR, you need to free it. On the other hand, if the CLR receives a pointer that is passed as [out] from native code, the CLR needs to free it. Clearly, in the first case, native code needs to do the de-allocation and in the second case, managed code needs to do de-allocation.
    Since this involves memory allocation and de-allocation, the biggest problem is what function to use. There are many to choose from: HeapAlloc/HeapFree, malloc/free, new/delete, and so on. However, since the CLR uses CoTaskMemAlloc/CoTaskMemFree in the non-BSTR case and SysStringAlloc/SysStringAllocByteLen/SysStringFree in the BSTR case, you'll have to use those functions. Otherwise, it is likely that you'll get a memory leak or a crash in certain versions of Windows®. We have seen cases where malloc'd memory was passed to the CLR and the program didn't crash in Windows XP but did crash in Windows Vista®.
    Besides those functions, a system-implemented IMalloc interface returned from CoGetMalloc also works fine because internally they are using the same heap. However, it is best to always stick with CoTaskMemAlloc/CoTaskMemFree and SysStringAlloc/ SysStringAllocByteLen/SysStringFree, since CoGetMalloc is subject to future change.
    Let's look at an example. The GetAnsiStringFromNativeCode takes a char ** argument as [in, out] and returns a char * as [out, retval]. For the char ** argument, it can choose to call CoTaskMemFree to free the memory that is allocated by the CLR, then allocate new memory by using CoTaskMemAlloc and overwrite the pointer with a new memory pointer. Later, the CLR will free the memory and create a copy for the managed string. As for the return value, it only needs to allocate a new piece of memory by using CoTaskMemAlloc and return it to the caller. After return, the newly allocated memory is now owned by the CLR. The CLR will first create a new managed string from it and then call CoTaskMemFree to free it.
    Let's consider the first option (see Figure 9). The corresponding C# function declaration is as follows:
    MARSHALLIB_API char *__stdcall GetAnsiStringFromNativeCode(char **arg)
    {
        char *szRet = (char *)::CoTaskMemAlloc(255);
        strcpy(szRet, "Returned String From Native Code");
    
        printf("Inside GetAnsiStringFromNativeCode: *arg = %s\n", *arg);
        printf("Inside GetAnsiStringFromNativeCode: CoTaskMemFree(*arg); 
            *arg = CoTaskMemAlloc(100); strcpy(*arg, \"Changed\")\n");
    
        ::CoTaskMemFree(*arg);
        *arg = (char *)::CoTaskMemAlloc(100);
        strcpy(*arg, "Changed");
    
        return szRet;
    }
    
    
    class Lib
    {
        [DllImport(@"MarshalLib.dll", CharSet= CharSet.Ansi)]
        public static extern string GetAnsiStringFromNativeCode(
            ref string inOutString);
    }
    
    Module [Lib]
    
        <DllImport("MarshalLib.dll", CharSet:=CharSet.Ansi)> _
        Public Function GetAnsiStringFromNativeCode(ByRef inoutString As String) As String
    
        End Function
    
    End Module
    
    When the following C# code makes a call to GetAnsiStringFromNativeCode
    string argStr = "Before";
    Console.WriteLine("Before GetAnsiStringFromNativeCode : argStr = \"" + 
        argStr + "\"");
    string retStr = Lib.GetAnsiStringFromNativeCode(ref argStr);
    Console.WriteLine("AnsiStringFromNativeCode() returns \"" + retStr + 
        "\"" );
    Console.WriteLine("After GetAnsiStringFromNativeCode : argStr = \"" + 
        argStr + "\"");
    
           Dim argStr = "Before"
            Console.WriteLine("Before  GetAnsiStringFromNativeCode: argStr = """ & argStr & """")
            Dim retStr = GetAnsiStringFromNativeCode(argStr)
            Console.WriteLine("AnsiStringFromNativeCode() returns """ & retStr & """")
            Console.WriteLine("After GetAnsiStringFromNativeCode: argStr = """ & argStr & """")
    
    
    the output is:
    Before GetAnsiStringFromNativeCode : argStr = "Before"
    Inside GetAnsiStringFromNativeCode: *arg = Before
    Inside GetAnsiStringFromNativeCode: CoTaskMemFree(*arg); *arg = CoTaskMemAlloc(100); strcpy(*arg, "Changed")
    AnsiStringFromNativeCode() returns "Returned String From Native Code"
    After GetAnsiStringFromNativeCode : argStr = "Changed"
    
    If the native function you are going to call doesn't follow this convention, you'll have to do the marshaling by yourself to avoid memory corruption. This could easily happen because the function for an unmanaged function could return whatever it wants; it can return the same piece of memory every time or return a new block of memory allocated by malloc/new, and so forth, again depending on the contract.
    Besides memory allocation, the size of the memory being passed in or out is also very important. As discussed in the StringBuilder case, it is very important to change the Capacity property so that the CLR can allocate a piece of memory big enough to hold the results. In addition, marshaling a string as [InAttribute, OutAttribute] (without out or ref and any other attribute) is a bad idea because you don't know whether the string will be big enough. You can use SizeParamIndex and SizeConst fields in MarshalAsAttribute to specify the size of the buffer. However, these attributes cannot be used when passing by reference.

    Reverse P/Invoke and Delegate Lifetime
    The CLR allows you to pass a delegate to the unmanaged world so that it can call the delegate as an unmanaged function pointer. In fact, what's happening is that the CLR creates a thunk, which forwards the calls from native code to the actual delegate, then to the real function (see Figure 10).
    Figure 10 Using a Thunk (Click the image for a larger view)
    Usually, you won't have to worry about the lifetime of delegates. Whenever you are passing a delegate to unmanaged code, the CLR will make sure the delegate is alive during the call.
    However, if the native code keeps a copy of the pointer beyond the span of the call and intends to call back through that pointer later, you might need to use GCHandle to explicitly prevent the garbage collector from collecting the delegate. We must warn you that a pinned GCHandle could have a significantly negative impact on program performance. Fortunately, in this case, you don't need to allocate a pinned GC handle, because the thunk is allocated in the unmanaged heap and is referencing the delegate indirectly through a reference known to the GC. Therefore, it is not possible for the thunk to move around, and native code should always be able to call the delegate through the unmanaged pointer if the delegate itself is alive.
    Marshal.GetFunctionPointerForDelegate can convert a delegate to a function pointer, but it doesn't do anything to guarantee the lifetime of the delegate. Consider the following function declaration:
    public delegate void PrintInteger(int n);
    
    [DllImport(@"MarshalLib.dll", EntryPoint="CallDelegate")]
    public static extern void CallDelegateDirectly(
        IntPtr printIntegerProc);
    
        Public Delegate Sub PrintInteger(ByVal n As Integer)
    
        <DllImport("MarshalLib.dll", EntryPoint:="CallDelegate")> _
        Public Sub CallDelegateDirectly(ByVal printIntegerProc As IntPtr)
    
        End Sub
    
    If you call Marshal.GetFunctionPointerForDelegate for it and store the returned IntPtr, then pass the IntPtr to the function you are going to call, like so:
    IntPtr printIntegerCallback = Marshal.GetFunctionPointerForDelegate(
        new Lib.PrintInteger(MyPrintInteger));
                
    
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    
    CallDelegateDirectly(printIntegerCallback);
    
            Dim printIntegerCallBack As IntPtr = Marshal.GetFunctionPointerForDelegate( _
                New PrintInteger(AddressOf MyPrintInteger))
            GC.Collect()
            GC.WaitForPendingFinalizers()
            GC.Collect()
            CallDelegateDirectly(printIntegerCallBack)
    
    It is possible that the delegate will be collected before you call CallDelegateDirectly, and you will get an MDA error that CallbackOnCollectedDelegate was detected. To fix that, you can either store a reference to the delegate in memory or allocate a GC handle.
    If native code returns an unmanaged function pointer to the CLR, it is the responsibility of native code to keep the actual function code around. Usually this isn't a problem unless the code is in a dynamically loaded DLL or generated on the fly.

    P/Invoke Interop Assistant
    Understanding and remembering all the attributes and rules described so far may seem a bit daunting. After all, most developers of managed code just need to be able to quickly figure out the P/Invoke signature for a Win32® API function, paste it in their code, and be done with it. That's where the P/Invoke Interop Assistant (available at the MSDN Magazine Web site) can help. This tool can efficiently assist with conversions from C++ to managed P/Invoke signatures as well as those in the opposite direction. It even comes with a database of Win32 functions, data types, and constants, so the common task of adding a Win32 P/Invoke to your C# or Visual Basic source file is made very easy. The tool package includes two command-line tools, SigImp and SigExp, which can be used for batch file processing. A GUI tool is also found in the package, which includes the functionalities of both tools.
    The GUI tool is handy for doing simple conversions. It contains three tabs: SigExp, SigImp Search, and SigImp Translate Snippet.
    SigExp converts managed signature to an unmanaged signature. It reflects over managed assemblies to find all P/Invoke declarations and COM imported types. From this input it produces the corresponding native C signatures (see Figure 11).
    Figure 11 P/Invoke Interop Assistant GUI Tool—SigExp (Click the image for a larger view)
    SigImp Search and SigImp Translate Snippet convert unmanaged signatures to managed signatures. They generate managed signatures and definitions in either C# or Visual Basic from native types, functions, constants, and snippets of manually entered native function signatures.
    SigImp Search allows users to choose the managed code language in which they want code generated and then select a native type, procedure, or constant to perform the generation from. The tool will display a list of supported types, methods, and constants collected from Windows SDK header files (see Figure 12).
    Figure 12 P/Invoke Interop Assistant GUI Tool—SigImp Search (Click the image for a larger view)
    SigImp Translate Snippet allows users to write their own native code snippet in the tool. The tool will then generate and display the managed code equivalent in the main window, as you see in Figure 13.
    Figure 13 P/Invoke Interop Assistant GUI Tool—SigImp Translate Snippet (Click the image for a larger view)
    For details of the GUI tool or the command-line tools in the P/Invoke Interop Assistant, please refer to the documentation packed with the tool.

    Give It a Try
    As you know, marshaling is a complex topic, and there are many techniques you can use to change the marshaling process to adapt to your own needs. We suggest you try some of the ideas presented here. They're sure to help you find your way through the maze we know as marshaling.

    Send your questions and comments to clrinout@microsoft.com.


    Yi Zhang is a Software Development Engineer on the Silverlight Shanghai team, which is part of the Server and Tool Business in China. He is currently focusing on interop and the CLR.

    Xiaoying Guo is the Program Manager on the Silverlight Shanghai team, which is part of the Server and Tools Business in China. She is currently focusing on the areas of unmanaged/managed Interop within the CLR, as well as the CoreCLR on the Mac. Xiaoying is also engaged in Silverlight local customer connection.
     
     
  • 相关阅读:
    3星|《失败课》:投资人写给创业者的经验谈,有点标题党
    3星|《给你一门人工智能入门生意经》:机器所知胜于其所能言传
    3星|《财经》2018年第5期:西伯利亚冻土层的猛犸象牙是合法的,一根能卖到数万美元
    3星|《增长黑客》:增长黑客是一个牵强的概念
    2星|《只管去做》:做年度计划的入门级介绍,信息浓度太低
    创业者融资过程中需要了解的大坑小坑:《风投的技术》,4星
    4星|吴军《见识》:李开复上级的工作经验、投资经验与人生忠告
    4星|《基因转》:从孟德尔、达尔文到人类胚胎转基因
    3星|《人机平台》:数字化时代的三大类新的再平衡:人脑与机器、产品与平台,以及核心与大众
    3星|《知识的边界》:知识存在于网络中,分歧永远存在
  • 原文地址:https://www.cnblogs.com/wuhenke/p/1655131.html
Copyright © 2011-2022 走看看