Я работаю над проектом, который должен иметь возможность вызывать функции в Win32 DLL.Однако имя DLL, функции и типы данных всех аргументов и возвращаемого типа не известны во время компиляции, поэтому использование DLLImport не вариант.По сути, эта процедура может вызывать любую функцию в любой DLL и передавать любые аргументы.После долгих поисков я смог успешно собрать некоторый код, который может это сделать, включая передачу чисел и строк в функцию и даже передачу числовых аргументов по ссылке.Тем не менее, я попал в кирпичную стену, пытаясь передать строки обратно из DLL.
Чтобы упростить тестирование, я использовал Visual Studio 6 для компиляции простой библиотеки Win32 DLL с именем PassCharPtr.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
Все работает отлично.Теперь, вот мой тестовый проект C # в VS2010, который пытается получить доступ к этой функции:
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]), но это возвращает пустую строку.
В этом коде все еще много дыр, но я надеюсь, что общая идеядостаточно ясноМожет кто-нибудь сказать мне, к какому типу данных должен быть объявлен первый аргумент, чтобы можно было успешно передавать строки как в эту функцию, так и из нее, и как выполнять любые необходимые преобразования для этого типа данных?