使用MethodInfo.Invoke在Win32 DLL和C#之间传递LPSTR

时间:2011-04-22 17:29:45

标签: c# string winapi pinvoke methodinfo

我正在开发一个需要能够在Win32 DLL中调用函数的项目。但是,在编译时不知道所有参数和返回类型的DLL,函数和数据类型的名称,因此使用DLLImport不是一个选项。基本上,这个例程可以在任何DLL中调用任何函数并传入任何参数。经过大量的搜索,我已经能够成功地组合一些可以做到这一点的代码,包括将数字和字符串传递给函数,甚至通过引用传递数字参数。但是,我试图从DLL传回字符串。

为简化测试,我使用Visual Studio 6编译一个名为PassCharPtr.dll的简单Win32 DLL,其中包含一个函数,如下所示:

PassCharPtr.def文件:

   EXPORTS
       TestPassCharPtr

PassCharPtr.h文件:

#include <windows.h>
extern "C" int __stdcall TestPassCharPtr(LPSTR, LONG);

PassCharPtr.cpp文件:

#include "PassCharPtr.h"

int WINAPI DllEntryPoint(HINSTANCE hinst,
                         unsigned long reason,
                         void*)
{
    return 1;
}


/*----------------------------------------------------------------*/
int __stdcall TestPassCharPtr(LPSTR szString, LONG cSize)
{
    MessageBox(0, szString, "Inside PassCharPtr.dll", 0);

    char buffer[] = "abcdefghijklmnopqrstuvwxyz";

    if (cSize > strlen(buffer))
    {
        strcpy(szString,buffer);
        return strlen(buffer);
    }
    return -cSize;
}

为了测试我的DLL,我创建了一个简单的VB6应用程序:

Private Declare Function TestPassCharPtr Lib "PassCharPtr" (ByVal buffer As String, ByVal lSize As Long) As Long

Private Sub btnTest_Click()
Dim sBuffer As String
Dim lResult As Long

    sBuffer = "This is a very long string!!!!!!!!!"
    lResult = TestPassCharPtr(sBuffer, Len(sBuffer))

Debug.Print "Result: "; lResult
Debug.Print "Buffer: "; Left(sBuffer, lResult)

End Sub

一切都很好。现在,这是我在VS2010中尝试访问此功能的C#测试项目:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

namespace TestPassCharPtr
{
    class Program
    {
        /// <summary>
        /// Define DLL and function to call. Setup data types for return value and arguments
        /// and setup values to pass into function.
        /// 
        /// All data types should be declared using C\C++ names to facilitate using
        /// existing Win32 API documentation to define function calls.
        /// 
        /// When passing a string to a function call, enclose the string in quotes ("")
        /// 
        /// </summary>
        /// <param name="args">Unused</param>
        static void Main(string[] args)
        {
            string fileName = "PassCharPtr.dll";
            string funcName = "TestPassCharPtr";

            string returnType = "int";

            // comma-delimited list of argument data types
            // using this declaration successfully passes string in
            // but generates exception when passing string back!
            string argTypesList = "char[], int";  

            // using this declaration fails to pass string in, but does
            // not generate an exception when passing string back!
            //string argTypesList = "LPSTR, int";              

            // comma-delimited list of argument values  
            string argValuesList = "\"This is a very long string!!!!\", 30";   

            TestDLLFunction(fileName, funcName, returnType, argTypesList, argValuesList);
            MessageBox.Show("Done");

        }

        /// <summary>
        /// Calls a DLL function.
        /// 
        /// Reference: http://www.pcreview.co.uk/forums/calling-native-c-function-using-definepinvokemethod-and-returning-calculated-value-pointer-reference-t2329473.html
        /// 
        /// </summary>
        /// <param name="dllFilename">Filename of DLL (excluding path!)</param>
        /// <param name="entryPoint">Function name</param>
        /// <param name="retType">Return value data type</param>
        /// <param name="argTypesList">Comma-delimited list of argument data types</param>
        /// <param name="argValuesList">Comma-delimited list of argument values</param>
        private static void TestDLLFunction(string dllPath, string entryPoint, string retType, string argTypesList, string argValuesList)
        {
            Type returnType = null;
            Type[] argTypesArray = null;
            object[] argValuesArray = null;
            object returnValue = null;

            // get return data type
            returnType = Type.GetType(ConvertToNetType(retType));

            // get argument data types for the function to call
            string[] argTypes = argTypesList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            if (argTypes.Length > 0)
            {
                // create a list of data types for each argument
                List<Type> listArgTypes = new List<Type>();
                foreach (var argType in argTypes)
                {
                    string netType = ConvertToNetType(argType);
                    string byRef = IsPointer(argType) ? "&" : "";

                    listArgTypes.Add(Type.GetType(netType + byRef));
                }
                // convert the list to an array
                argTypesArray = listArgTypes.ToArray<Type>();

                // get values to pass as arguments to the function
                string[] argValues = argValuesList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                // remove quotes from strings
                for (int i = 0; i < argValues.Length; i++)
                {
                    argValues[i] = argValues[i].Replace("\"", "").Trim();
                }
                argValuesArray = argValues.ToArray<object>();

                // verify argument data types count and argument values count match!
                if (argValuesArray.Length != argTypesArray.Length)
                {
                    Console.WriteLine(string.Format("The number of parameter types ({0}) does not match the number of parameter values ({1}).", argTypesArray.Length, argValuesArray.Length));
                    return;
                }

                // convert all argument values to the proper data types
                for (int i = 0; i < argValuesArray.Length; i++)
                {
                    if (argTypesArray[i] == Type.GetType("System.IntPtr&"))
                    {
                        argValuesArray[i] = (IntPtr)0;
                    }
                    else
                    {
                        argValuesArray[i] = ConvertParameter(argValuesArray[i], argTypesArray[i]);
                    }
                }
            }
            else
            {
                argTypesArray = null;
                argValuesArray = null;
            }

            // Create a dynamic assembly and a dynamic module
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = dllPath;

            AssemblyBuilder dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

            ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("tempModule");

            // Dynamically construct a global PInvoke signature using the input information
            MethodBuilder dynamicMethod = dynamicModule.DefinePInvokeMethod(entryPoint, dllPath,
                MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.PinvokeImpl,
                CallingConventions.Standard, returnType, argTypesArray, CallingConvention.Winapi, CharSet.Ansi);

            // Add PreserveSig to the method implementation flags. NOTE: If this line
            // is commented out, the return value will be zero when the method is invoked.
            dynamicMethod.SetImplementationFlags(dynamicMethod.GetMethodImplementationFlags() | MethodImplAttributes.PreserveSig);

            // This global method is now complete
            dynamicModule.CreateGlobalFunctions();

            // Get a MethodInfo for the PInvoke method
            MethodInfo mi = dynamicModule.GetMethod(entryPoint);

            // Invoke the function
            try
            {
                returnValue = mi.Invoke(null, argValuesArray);
            }
            catch (Exception ex)
            {
                Console.WriteLine(string.Format("Error: {0}", ex.Message));
                if (ex.InnerException != null)
                {
                    Console.WriteLine(string.Format("  Error: {0}", ex.InnerException.Message));
                }
            }

            if (returnValue != null)
            {
                Console.WriteLine(string.Format("Return value: {0}", returnValue.ToString()));
            }

            if (argValuesArray != null)
            {
                for (int i = 0; i < argValuesArray.Length; i++)
                {
                    if (argValuesArray[i] != null)
                    {
                        Console.WriteLine(string.Format("Argument {0}: {1}", i, argValuesArray[i].ToString()));
                    }
                }
            }
        }

        /// <summary>
        /// Converts a string to another data type.
        /// </summary>
        /// <param name="value">Value to be converted</param>
        /// <param name="dataType">Data type to convert to</param>
        /// <returns>Converted value</returns>
        private static object ConvertParameter(object value, Type dataType)
        {
            // determine the base data type (remove "&" from end of "by reference" data types)
            string baseDataType = dataType.ToString();
            if (baseDataType.EndsWith("&"))
            {
                baseDataType = baseDataType.Substring(0, baseDataType.Length - 1);
            }
            return Convert.ChangeType(value, Type.GetType(baseDataType));
        }

        /// <summary>
        /// Determines whether the indicated native data type is a pointer
        /// </summary>
        /// <param name="dataType">Native (unmanaged) data type</param>
        /// <returns>true if data type is a pointer; false otherwise</returns>
        private static bool IsPointer(string dataType)
        {
            string lowerDataType = dataType.ToLower();
            if (lowerDataType.StartsWith("lp") || lowerDataType.EndsWith("*"))
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Convert unmanaged data type names to .NET data type names
        ///
        /// (for simplicity, all types unused by this example were removed)
        ///
        /// </summary>
        /// <param name="type">Unmanaged data type name</param>
        /// <returns>Corresponding .NET data type name</returns>
        private static string ConvertToNetType(string type)
        {
            string lowerType = type.ToLower();

            if (lowerType.Contains("int"))
            {
                return "System.Int32";
            }
            else if (lowerType.Contains("lpstr"))
            {
                return "System.IntPtr";
            }
            else if (lowerType.Contains("char[]"))
            {
                return "System.String";
            }
            return "";
        }

    }
}

如果我将第一个参数声明为char [](System.String),我可以成功地将字符串传递给函数,但是当DLL尝试用字符串替换该字符串时,它会生成异常(访问受保护的内存)回来。

如果我将第一个参数声明为LPSTR(System.IntPtr),我无法将字符串传递给函数。但是,从调用返回时,argValuesArray [0]包含看似是地址的内容。我还没弄清楚如何将该地址转换为返回的字符串。我尝试过使用String mvValue = Marshal.PtrToStringAnsi((IntPtr)argValuesArray [0]),但这会返回一个空字符串。

此代码中仍有很多漏洞,但我希望这个概念足够清晰。任何人都可以告诉我应该声明第一个参数的数据类型,以便能够成功地将字符串传入和传出此函数以及如何对该数据类型进行任何必要的转换?

1 个答案:

答案 0 :(得分:2)

LPSTR通常被编组为StringBuilder。