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

发布于 2024-11-03 11:04:38 字数 11828 浏览 6 评论 0原文

我正在开发一个项目,需要能够调用 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),我可以成功地将字符串传递到该函数中,但它会生成一个异常(访问 protected当 DLL 尝试用要返回的字符串替换该字符串时。

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

这段代码仍然有很多漏洞,但我希望总体思路足够清晰。谁能告诉我第一个参数应该声明什么数据类型才能成功地将字符串传入和传出该函数以及如何对该数据类型进行任何必要的转换?

I am working on a project that needs to be able to call functions in Win32 DLLs. However, the name of the DLL, function, and data types of all arguments and return type are not known at compile time, so using DLLImport is not an option. Basically, this routine could feasibly call any function in any DLL and pass in any arguments. After a lot of searching, I have been able to successfully put together some code that can do this, including passing numerics and strings into the function and even passing numeric arguments by reference. However, I have hit a brick wall trying to pass strings back from the DLL.

To simplify testing, I used Visual Studio 6 to compile a simple Win32 DLL called PassCharPtr.dll containing one function as follows:

PassCharPtr.def file:

   EXPORTS
       TestPassCharPtr

PassCharPtr.h file:

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

PassCharPtr.cpp file:

#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;
}

To test my DLL, I created a simple VB6 application:

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

Everything is working great. Now, here's my C# test project in VS2010 that attempts to access this function:

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 "";
        }

    }
}

If I declare the first argument as char[] (System.String), I can successfully pass a string into the function, but it generates an exception (accessing protected memory) when the DLL attempts to replace that string with the string to return.

If I declare the first argument as LPSTR (System.IntPtr), I cannot pass a string into the function. However, upon returning from the call, argValuesArray[0] contains what appears to be an address. I have not been able to figure out how to convert that address into the returned string yet. I have tried using String mvValue = Marshal.PtrToStringAnsi((IntPtr)argValuesArray[0]), but this returns an empty string.

There are still a lot of holes in this code, but I hope the general idea is clear enough. Can anyone tell me what data type the first argument should be declared as to be able to pass strings both into and out of this function successfully and how to do any necessary conversions on that data type?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

我的奇迹 2024-11-10 11:04:38

LPSTR 通常被编组为 StringBuilder。

LPSTR is generally marshalled as StringBuilder.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文