Wrap native DLL для C#


Я написал C++ DLL, и теперь мне нужно вызвать собственную функцию из управляемого приложения.

Экспортированная собственная функция выглядит следующим образом:

extern "C" __declspec(dllexport) 
bool NativeMethod(char *param1, char *param2, char *result);

Итак, из C# я вызову эту функцию, передавая 2 входных парама, 1 выходной парам, и, очевидно, я прочитаю возвращаемое значение bool.

Я пытался обернуть все это разными способами, но всегда получал исключение PInvokeStackImbalance. Единственный известный мне способ вызвать собственную функцию-это применить CallingConvention = CallingConvention.Cdecl) к объявлению функции .NET. Однако в этом смысле я не такой возможность считывать выходные параметры (это всегда пустая строка), а также возвращаемое значение всегда true.
5 7

5 ответов:

Во-первых, я бы настроил прототип вашей родной функции.

Поскольку эта функция имеет интерфейсC , вы должны использовать тип C для булевых значений, а не Тип C++, как bool. Вы можете использовать тип Win32 BOOL.

Более того, как это происходит в настоящее время, ваша функция склонна к переполнению буфера: лучше добавить другой параметр, чтобы указать максимальный размер строкового буфера назначения result.

Отметим также, что широко распространенный вызов соглашение для DLL, экспортирующих чистые функции интерфейса C (как и множество функций Win32 API), является __stdcall (не __cdecl). Я бы тоже этим воспользовался.

Последнее, так как первые два параметра являютсявходными строками, вы можете использовать const, чтобы сделать его понятным и обеспечить const-корректность.

Итак, я бы сделал прототип экспортируемой нативной функции следующим образом:

extern "C" __declspec(dllexport) 
BOOL __stdcall NativeFunction(
    const char *in1, 
    const char *in2, 
    char *result, 
    int resultMaxSize);

Затем, на стороне C#, вы можете использовать следующий P / Invoke:

   [DllImport(
        "NativeDll.dll", 
        CharSet = CharSet.Ansi, 
        CallingConvention = CallingConvention.StdCall)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool NativeFunction(
        string in1,
        string in2,
        StringBuilder result, 
        int resultMaxSize);

Обратите внимание, что для используется строка вывода A StringBuilder.

Обратите также внимание, что CharSet = CharSet.Ansi используется для маршалинга строк Unicode UTF-16 C#в ANSI (обратите внимание, что преобразование является с потерями - Если вы хотите преобразование без потерь, просто используйте строки wchar_t* на стороне C++).

Я сделал тест с простой c++ native DLL:

// NativeDll.cpp

#include <string.h>
#include <windows.h>

extern "C" __declspec(dllexport) 
BOOL __stdcall NativeFunction(
    const char *in1, 
    const char *in2, 
    char *result, 
    int resultMaxSize)
{
    // Parameter check
    if (in1 == nullptr 
        || in2 == nullptr 
        || result == nullptr 
        || resultMaxSize <= 0)
        return FALSE;

    // result = in1 + in2
    strcpy_s(result, resultMaxSize, in1);
    strcat_s(result, resultMaxSize, in2);

    // All right
    return TRUE;
}

И он успешно вызывается следующим кодом консольного приложения C#:

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace CSharpClient
{
    class Program
    {
        [DllImport(
            "NativeDll.dll", 
            CharSet = CharSet.Ansi, 
            CallingConvention = CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool NativeFunction(
            string in1,
            string in2,
            StringBuilder result, 
            int resultMaxSize);

        static void Main(string[] args)
        {
            var result = new StringBuilder(200);
            if (! NativeFunction("Hello", " world!", result, result.Capacity))
            {
                Console.WriteLine("Error.");
                return;
            }

            Console.WriteLine(result.ToString());
        }
    }
}

Вы избавите себя от многих головных болей P/Invoke, если вместо этого будете использовать COM-взаимодействие. Поместите метод в COM-интерфейс и измените сигнатуру, чтобы следовать соглашениям COM:

interface ISomeInterface : IUnknown
{
    HRESULT NativeMethod([in] BSTR bstrParam1, [in] BSTR bstrParam2, 
                         [out] BSTR* pbstrParam3, [out, retval] VARIANT_BOOL* pvbResult);
}

Я изменил char* на BSTR и bool на VARIANT_BOOL, потому что это типы, используемые COM для строк и bools соответственно. Кроме того, все COM-методы должны возвращать значение HRESULT. Если вы хотите получить "фактическое" возвращаемое значение, вы должны добавить его как последнее out параметр, а также пометить его атрибутомretval .

Затем добавьте ссылку на COM-компонент из проекта C#, и вы получите интуитивно понятную сигнатуру C# без необходимости угадывать, как сопоставить типы C++ с типами C#:

bool NativeMethod(string bstrParam1, string bstrParam2, out string pbstrParam3)

(Вот как это выглядит в Обозревателе объектов.)

Почему обратите внимание на использование маршалинга кода .Net с помощью DLLImport, например, следующее

[DllImport(@"C:\TestLib.dll")]
        public static extern void ProtectDocument(
            out [MarshalAs(UnmanagedType.LPStr)]string validToDate);

И затем вы можете вызвать функцию как локальную функцию следующим образом

string x=string.empty;
ProtectDocument(out x); 
    [DllImport("MyDll.dll", EntryPoint = "NativeMethod", CallingConvention = CallingConvention.Cdecl)]
    static extern bool NativeMethod(
        [MarshalAs(UnmanagedType.LPStr)]string param1,
        [MarshalAs(UnmanagedType.LPStr)]string param2,
        [MarshalAs(UnmanagedType.LPStr)]string param3);

Замените LPStr на LPWStr, Если вы работаете с широкими символами.

[DllImport("MyLibrary.dll", EntryPoint = "NativeMethod")]
public static unsafe extern bool NativeMethod(
    [MarshalAs(UnmanagedType.LPStr)] string param1,
    [MarshalAs(UnmanagedType.LPStr)] string param2,
    [MarshalAs(UnmanagedType.LPStr)] char *param3);

Выходной параметр должен быть char *, так как строки C# неизменяемы. Вы вызываете метод (в небезопасном контексте) следующим образом:

char[] output = new char[100];
fixed (char *param = &output[0])
{
    NativeMethod("blahblah", "blahblah", param);
}

Если выходной параметр не является строкой, а только одним символом, в этом случае вы можете просто сделать следующее:

[DllImport("MyLibrary.dll", EntryPoint = "NativeMethod")]
public static unsafe extern bool NativeMethod(
    [MarshalAs(UnmanagedType.LPStr)] string param1,
    [MarshalAs(UnmanagedType.LPStr)] string param2,
    out char param3);

И вы можете просто использовать его следующим образом:

char output;
NativeMethod("blahblah", "blahblah", out output);