sitelogo

By

Advanced marshaling в C#

Сейчас работаю над сложным, но интересным проектом в C# с кучей хаков и перехватчиков API и COM интерфейсов. И у меня появилось несколько новых вещей которые я бы хотел записать. Надеюсь поможет кому-то при написании чего-то подобного, не сорвав сроки. Подобная документация конечно имеется в интернете (маршалинг в С#) тем не менее я хочу расширить тему и остановиться на конкретных примерах импорта из DirectX и других библиотек.

В сети есть полно разных оберток для использования этих технологий, и все же, мне не нравится использовать непереносимые, монструозные решения типа C++/CLI или кучи дополнительных DLL, когда размер DLL обертки имеет размер, приближенный к самой DLL, которую импортируем (пример — SlimDX).

Выход из ситуации — использование только C# кода, т.к. он может выполняться на любом процессоре. Правда, полностью отказаться от нативного кода не получится — бывает, требуется высокая скорость выполнения каких-либо операций, например нужен JPEG кодировщик, использующий SIMD инструкции процессора или CUDA и тому подобное, но эти либы можно хотя-бы загружать выборочно, в зависимости на какой платформе работаем. Для реализации своей идеи приходится как всегда… написать кучу собственных оберток. Ближе к делу.

Маршалинг в C# может быть использован для импорта практически любых системных библиотек Windows и подключения своих собственных. Некоторые считают, что маршалинг занимает много времени выполнения, но это не так — читайте про Memory Pinning и Memory copying, для оптимизации маршалинга нужно использовать именно Pinning, тогда данные строк и массивов не будут копироваться и вы получите максимальную скорость, как и при обычном импорте в C++, проверено лично.

Как импортировать функцию из DLL библиотеки?

Начнем с простого.

C++:

1
2
3
4
5
6
7
8
9
HANDLE WINAPI CreateRemoteThread(
  _In_   HANDLE hProcess,
  _In_   LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_   SIZE_T dwStackSize,
  _In_   LPTHREAD_START_ROUTINE lpStartAddress,
  _In_   LPVOID lpParameter,
  _In_   DWORD dwCreationFlags,
  _Out_  LPDWORD lpThreadId
);

C#:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace Wrappers
{
    public class NativeAPI
    {
        [DllImport("kernel32", CallingConvention=CallingConvention.Winapi)]
        public static extern IntPtr CreateRemoteThread(
            [In, MarshalAs(UnmanagedType.SysInt)] IntPtr hProcess,
            [In, MarshalAs(UnmanagedType.SysInt)] IntPtr lpThreadAttributes,
            [In, MarshalAs(UnmanagedType.U4)] uint dwStackSize,
            [In, MarshalAs(UnmanagedType.SysInt)] IntPtr lpStartAddress,
            [In, MarshalAs(UnmanagedType.SysInt)] IntPtr lpParameter,
            [In, MarshalAs(UnmanagedType.U4)] uint dwCreationFlags,
            [Out, MarshalAs(UnmanagedType.SysInt)] out IntPtr lpThreadId
        );
    }
}

В неуправляемом коде функции могут иметь разные соглашения вызовов, это можно указать параметром CallingConvention, по умолчанию он имеет значение WinApi, поэтому в данном случае он необязателен, но если импортируем из Си библиотеки и забыли указать Cdecl, программа вылетит с ошибкой. Атрибуты слева указывают, как функция должна быть импортирована. Параметры функций бывают трех типов — In, Out, и In\Out вместе взятые. Справа от атрибута описывается представление функции в C# коде, которая превращается в статический метод класса. Типы данных слева и справа должны соответствовать, In функции имеют ключевое слово in или не имеют его вовсе, функции с атрибутом Out ключевое слово out, с атрибутами In, Out имеют ключевое слово ref. Если параметр является простым, атрибуты можно вообще не указывать. Есть отличный сайт-справочник по сигнатурам стандартных API функций pinvoke.net.

Чем отличаются параметры in, out и ref?

Параметры In передают простые типы в неизменном виде (числа, помещающиеся в регистр CPU, указатели), для сложных типов создается копия обьекта и передается указатель на эти данные, массив или строку или создается указатель прямо на объект.

Параметры out передают указатель на только что выделенный обьект, инициализированный значением по умолчанию. Все типы данных передаются по указателю.

Параметры ref — то же что и out, но передают указатель на скопированный обьект, который уже был инициализирован пользователем или передает указатель на сам обьект.. Для структур данных он меняет поведение при передаче обьекта.

Как передавать сложные структуры данных?

Буферы фиксированного размера:
C++

1
2
3
4
5
6
typedef struct _D3DGAMMARAMP
{
    WORD                red  [256];
    WORD                green[256];
    WORD                blue [256];
} D3DGAMMARAMP;

C#

1
2
3
4
5
6
7
8
9
[StructLayout(LayoutKind.Sequential)]
public struct D3DGAMMARAMP {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public ushort[] red;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public ushort[] green;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public ushort[] blue;
}

Структуры с объединениями (union)

C++

1
2
3
4
5
6
7
8
9
10
typedef struct {
  WAVEFORMATEX Format;
  union {
    WORD wValidBitsPerSample;
    WORD wSamplesPerBlock;
    WORD wReserved;
  } Samples;
  DWORD        dwChannelMask;
  GUID         SubFormat;
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;

C#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct WAVEFORMATEXTENSIBLE {
    [FieldOffset(0)]
    public WAVEFORMATEX Format;
    [FieldOffset(18)]
    public ushort wValidBitsPerSample;
    [FieldOffset(20)]
    public ushort wSamplesPerBlock;
    [FieldOffset(22)]
    public ushort wReserved;
    [FieldOffset(18)]
    public uint dwChannelMask;
    [FieldOffset(24)]
    public Guid SubFormat;
}

Структуры с указателями на функции:

C

1
2
3
4
5
6
7
8
struct jpeg_destination_mgr {
  JOCTET * next_output_byte;	/* => next byte to write in buffer */
  size_t free_in_buffer;	/* # of byte spaces remaining in buffer */
 
  JMETHOD(void, init_destination, (j_compress_ptr cinfo));
  JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo));
  JMETHOD(void, term_destination, (j_compress_ptr cinfo));
};

C#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct jpeg_destination_mgr {
    public IntPtr next_output_byte;
    public uint free_in_buffer;
    public init_destination_delegate init_destination;
    public empty_output_buffer_delegate empty_utput_buffer;
    public term_destination_delegate term_destination;
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void init_destination_delegate(
        [In, Out, MarshalAs(UnmanagedType.Struct)] ref jpeg_compress_struct cinfo
    );
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate bool empty_output_buffer_delegate(
        [In, Out, MarshalAs(UnmanagedType.Struct)] ref jpeg_compress_struct cinfo
    );
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void term_destination_delegate(
        [In, Out, MarshalAs(UnmanagedType.Struct)] ref jpeg_compress_struct cinfo
    );
}

Загадочный тип BOOL:

В этот тип по умолчанию конвертируется управляемый тип bool, который будет занимать 4 байта. Для функций это нормально, т.к. параметры передаются всегда в 32 битных регистрах и пофиг, 1 байт занимает BOOL или 4. Для структур данных это очень важно, т.к. нарушится выравнивание. Поэтому нужно объявлять так:

1
2
    [MarshalAs(UnmanagedType.U1)]
    public byte somefield;

Передача структур в параметрах методов\функций:

Нужно передавать параметры Struct с атрибутом ref, иначе поля структуры передаются по отдельности как обычные параметры через стек, а не через указатель.

Как импортировать COM интерфейсы?
Открываем *.h или *.idl файл и берем всю информацию оттуда. C# интерфейсы могут описывать и обычные COM интерфейсы. Если над методом не стоит атрибут [PreserveSig], C# будет автоматически проверять коды ошибок HRESULT и выбрасывать исключения. Также параметры out могут быть преобразованы прямо в return value, если указан тип вместо void. Если стандартное поведение некорректно, нужно поставить атрибут PreserveSig и обрабатывать все самому.
C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
MIDL_INTERFACE("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2")
IAudioClient : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE Initialize( 
        /* [annotation][in] */ 
        __in  AUDCLNT_SHAREMODE ShareMode,
        /* [annotation][in] */ 
        __in  DWORD StreamFlags,
        /* [annotation][in] */ 
        __in  REFERENCE_TIME hnsBufferDuration,
        /* [annotation][in] */ 
        __in  REFERENCE_TIME hnsPeriodicity,
        /* [annotation][in] */ 
        __in  const WAVEFORMATEX *pFormat,
        /* [annotation][in] */ 
        __in_opt  LPCGUID AudioSessionGuid) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE GetBufferSize( 
        /* [annotation][out] */ 
        __out  UINT32 *pNumBufferFrames) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE GetStreamLatency( 
        /* [annotation][out] */ 
        __out  REFERENCE_TIME *phnsLatency) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE GetCurrentPadding( 
        /* [annotation][out] */ 
        __out  UINT32 *pNumPaddingFrames) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE IsFormatSupported( 
        /* [annotation][in] */ 
        __in  AUDCLNT_SHAREMODE ShareMode,
        /* [annotation][in] */ 
        __in  const WAVEFORMATEX *pFormat,
        /* [unique][annotation][out] */ 
        __out_opt  WAVEFORMATEX **ppClosestMatch) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE GetMixFormat( 
        /* [annotation][out] */ 
        __out  WAVEFORMATEX **ppDeviceFormat) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE GetDevicePeriod( 
        /* [annotation][out] */ 
        __out_opt  REFERENCE_TIME *phnsDefaultDevicePeriod,
        /* [annotation][out] */ 
        __out_opt  REFERENCE_TIME *phnsMinimumDevicePeriod) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE Start( void) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE Stop( void) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE Reset( void) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE SetEventHandle( 
        /* [in] */ HANDLE eventHandle) = 0;
 
    virtual HRESULT STDMETHODCALLTYPE GetService( 
        /* [annotation][in] */ 
        __in  REFIID riid,
        /* [annotation][iid_is][out] */ 
        __out  void **ppv) = 0;
 
};

C#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[ComImport, Guid("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAudioClient {
    void Initialize( 
        [In, MarshalAs(UnmanagedType.U4)] AUDCLNT_SHAREMODE ShareMode,
        [In, MarshalAs(UnmanagedType.U4)] AUDCLNT_FLAGS StreamFlags,
        [In, MarshalAs(UnmanagedType.U8)] long hnsBufferDuration,
        [In, MarshalAs(UnmanagedType.U8)] long hnsPeriodicity,
        [In, Out, MarshalAs(UnmanagedType.Struct)] ref WAVEFORMATEX pFormat,
        [In, MarshalAs(UnmanagedType.SysInt)] IntPtr AudioSessionGuid
    );
    [return: MarshalAs(UnmanagedType.U4)]
    uint GetBufferSize();
    void GetStreamLatency(
        [Out, MarshalAs(UnmanagedType.U8)] out long phnsLatency
    );
    void GetCurrentPadding( 
        [Out, MarshalAs(UnmanagedType.U4)] out uint pNumPaddingFrames
    );
    void IsFormatSupported( 
        [In, MarshalAs(UnmanagedType.U4)] AUDCLNT_SHAREMODE ShareMode,
        [In, MarshalAs(UnmanagedType.Struct)] WAVEFORMATEX pFormat,
        [Out, MarshalAs(UnmanagedType.Struct)] out WAVEFORMATEX ppClosestMatch
    );
    void GetMixFormat([In, Out] ref IntPtr ppFmt);
    void GetDevicePeriod( 
        [Out, MarshalAs(UnmanagedType.U8)] out long phnsDefaultDevicePeriod, // opt
        [Out, MarshalAs(UnmanagedType.U8)] out long phnsMinimumDevicePeriod // opt
    );
    void Start();
    void Stop();
    void Reset();
    void SetEventHandle( 
        [In, MarshalAs(UnmanagedType.SysInt)] IntPtr eventHandle
    );
    [return: MarshalAs(UnmanagedType.Interface)]
    object GetService(  
        [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid
    );
}

Еще один, менее красивый способ в стиле Си, но тоже отлично работает, хорош тем, что мы можем преобразовать указатель IntPtr в интерфейс, используя Marshal.PtrToStructure(). COM интерфейс можно представить как обычную структуру данных, где данными является массив указателей на функции. Для большей элегантности кода лучше все же использовать первый способ, используя его также можно получить копию VTBL для разных манипуляций.
C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DECLARE_INTERFACE_(IDirect3DResource9, IUnknown)
{
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)(THIS) PURE;
    STDMETHOD_(ULONG,Release)(THIS) PURE;
 
    /*** IDirect3DResource9 methods ***/
    STDMETHOD(GetDevice)(THIS_ IDirect3DDevice9** ppDevice) PURE;
    STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE;
    STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE;
    STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE;
    STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) PURE;
    STDMETHOD_(DWORD, GetPriority)(THIS) PURE;
    STDMETHOD_(void, PreLoad)(THIS) PURE;
    STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) PURE;
};

C#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[StructLayout(LayoutKind.Sequential)]
class IUnknownMethods {
    public QueryInterfaceDelegate QueryInterface;
    public AddRefDelegate AddRef;
    public ReleaseDelegate Release;
 
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate int QueryInterfaceDelegate(IntPtr _this, IntPtr pUnk, ref Guid iid, out IntPtr ppv);
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate int AddRefDelegate(IntPtr _this);
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate int ReleaseDelegate(IntPtr _this);
}
[StructLayout(LayoutKind.Sequential)]
class IDirect3D9ResourceMethods : IUnknownMethods {
    public GetDeviceDelegate GetDevice;
    public SetPrivateDataDelegate SetPrivateData;
    public GetPrivateDataDelegate GetPrivateData;
    public FreePrivateDataDelegate FreePrivateData;
    public SetPriorityDelegate SetPriority;
    public GetPriorityDelegate GetPriority;
    public PreLoadDelegate PreLoad;
    public GetTypeDelegate GetType_;
    /*** IDirect3DResource9 methods ***/
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate int GetDeviceDelegate(IntPtr THIS_, out IntPtr ppDevice); //IDirect3DDevice9**
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate int SetPrivateDataDelegate(IntPtr THIS_, IntPtr refguid, IntPtr pData, uint SizeOfData, uint Flags);
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate int GetPrivateDataDelegate(IntPtr THIS_, IntPtr refguid, out IntPtr pData, ref uint pSizeOfData);
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate int FreePrivateDataDelegate(IntPtr THIS_, IntPtr refguid);
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate uint SetPriorityDelegate(IntPtr THIS_, uint PriorityNew);
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate uint GetPriorityDelegate(IntPtr THIS);
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate void PreLoadDelegate(IntPtr THIS);
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate D3DRESOURCETYPE GetTypeDelegate(IntPtr THIS);
}

Метод 3: использовать автоматические средства VisualStudio, при этом будет создана дополнительная библиотека DLL.

Как получить указатель IntPtr на COM интерфейс?

Если используем вариант 1 (обьявляем интерфейс как положено с помощью ключевого слова interface), то нужно использовать методы Marshal.GetComInterfaceForObject/Marshal.Release. Первый метод увеличивает внутренний счетчик обьекта на 1 и возвращает указатель на VTBL, второй уменьшает счетчик на 1. Методы используют вызов IUnknown.QueryInterface.

Если используем вариант 2 (через представление VTBL как структуру), то используем Marshal.PtrToStructure/Marshal.StructureToPtr. В этом случае никаких методов интерфейса не вызывается, мы должны заботиться о ресурсах вручную, что не является стилем C#.

Как создать обьект, если его COM интерфейс зарегистрирован (создается в C++ с помощью CoCreateInstance)?

Нужно обьявить класс и пометить его атрибутом ComImport. Тогда все действия за нас выполнит C#, если мы просто создадим новый экземпляр класса.

1
2
3
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
public class CoMMDeviceEnumerator {
}

Затем делаем что-то вроде:

1
2
3
4
5
public MMDeviceEnumerator()
{
    coclass = new CoMMDeviceEnumerator();
    intf = (IMMDeviceEnumerator)coclass;
}

Как создать обьект, если его COM интерфейс не зарегистрирован, а выдается простой функцией?

Тогда нужно эту функцию импортировать примерно так:

1
2
3
[DllImport("d3d9", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
    [return: MarshalAs(UnmanagedType.Interface)]
    private static extern IDirect3D9 Direct3DCreate9(uint SDKVersion);


13 Responses to Advanced marshaling в C#

  1. Almunt пишет:

    Лучший материал, пока что найденный мною по интересующему вопросу.
    Кстати,
    class IDirect3D9ResourceMethods : IUnknownMethods {}
    IUnknownMethods, а здесь что?

    • viron пишет:

      Дополнил недостающую информацию об IUnknownMethods. Это базовый класс, который реализует методы IUnknown. При представлении VTBL как структуры в C# наследование кое-как работает.

  2. Alexey Smorkalov пишет:

    Большое спасибо за статью!

  3. Алексей пишет:

    Автору респект.
    Нужна помощь по теме. Помогите настроить маршалинг для функции:
    bool EXPORT_FUNCTION_SHELL SL_SIGN
    (
    char *inId,
    char *inMsg,
    int inMsgLen,
    bool inMsgDetached,
    SsfCharstring *outSign,
    int *outSignLen
    ) ;
    dll на с++, вызов из С#.

    • viron пишет:

      Немного отошел от этой темы (последнее время сидел на Delphi). У вас отсутствует информация,
      чтобы правильно настроить маршалинг. Что за макрос EXPORT_FUNCTION_SHELL? SsfCharstring —
      это массив?

      Могу лишь предложить что-то вроде этого:
      class ???
      {
      [DllImport(@»???.dll», CallingConvention=CallingConvention.???, CharSet=CharSet.Ansi)]
      public static extern bool SL_SIGN(
      [In, MarshalAs(UnmanagedType.LPSTR)] string inId,
      [In, MarshalAs(UnmanagedType.LPSTR)] string inMsg,
      [In, MarshalAs(UnmanagedType.I4)] int inMsgLen,
      [In, MarshalAs(UnmanagedType.Bool)] bool inMsgDetached,
      [Out, MarshalAs(UnmanagedType.???), SizeParamIndex=5] out ??? outSign,
      [In, Out, MarshalAs(UnmanagedType.I4)] ref int outSignLen
      );
      }

      • Алексей пишет:

        Спасибо за ответ.
        По очереди:
        1. SsfCharstring описан в С++ так:
        typedef char* SsfCharstring;

        2. CallingConvention = CallingConvention.StdCall

        3. Интересует вопрос, почему [In, Out, MarshalAs(UnmanagedType.I4)] ref int outSignLen указано в маршалинге In, Out?

        Спасибо еще раз.

        • Алексей пишет:

          //Описание прототипа функции
          [DllImport(@»libSecShellSrv.dll», CallingConvention = CallingConvention.StdCall, CharSet=CharSet.Ansi)]
          public static extern bool SL_SIGN(
          [In, MarshalAs(UnmanagedType.LPStr)] string inId, //char* inId,
          [In, MarshalAs(UnmanagedType.LPStr)] string inMsg, //char* inMsg,
          [In, MarshalAs(UnmanagedType.I4)] int inMsgLen, //int inMsgLen,
          [In, MarshalAs(UnmanagedType.Bool)] bool inMsgDetached,
          [Out, MarshalAs(UnmanagedType.LPStr)] out string outSign, //SsfCharstring *outSign,
          [In, Out, MarshalAs(UnmanagedType.I4)] ref int outSignLen //int *outSignLen
          );

          //Вызов в коде
          string signedMsg;
          int signedMsgLen=0;
          bool result = SL_SIGN(«310006», «test», 4, false, out signedMsg, ref signedMsgLen);

          //Получаю ошибку
          Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

          Не понятно, что не так. Научите новичка.

          • viron пишет:

            Это значит, что где-то есть переполнение буфера и ваша функция вызывает исключение. Скорее всего, проблема в параметрах 4 и 5. Наверно, не выделяется достаточное количество памяти под строковый буфер #4. Принимая тот факт, что вы передаете в функцию размер буфера 0, функция не должна ничего писать в этот буфер. Изучите документацию для вашей функции — нужно знать ее поведение, каким образом ей нужно передавать буфер. Есть такие ф-и, которые сами выделяют память под строки, а программист сам должен освобождать эту память. Тогда маршалинг для этой ф-и остается неверным.

            Для выходных строк часто используют класс StringBuilder:
            [Out, MarshalAs(UnmanagedType.LPStr)] out StringBuilder outSign, //SsfCharstring *outSign

            Увы, вам придется экспериментировать, чтобы избежать переполнения буфера и прочих ошибок, которые могут возникнуть.

        • viron пишет:

          1. Тогда 4-й параметр (отсчет с нуля) наверно будет таким:
          [Out, MarshalAs(UnmanagedType.LPSTR), SizeParamIndex=5] out char[] outSign
          3. Потому, что возможно, что вам надо выделить буфер под строку (параметр 4) и размер этого буфера занести в переменную типа int перед вызовом вашей функции. Неуправляемая функция будет копировать данные в этот буфер, основываясь на его размере из параметра номер 5. Будьте внимательны при маршалинге подобных функций, неправильный маршалинг запросто приведет к утечкам памяти или переполнению буфера.

          Когда вы ставите только атрибут Out, то CLR инициализирует значение переменной по умолчанию. Для числовых типов это значение 0. Когда указываете In + Out + ref, CLR передает в неуправляемую функцию значение, которое было инициализировано вами, а затем неуправляемый код еще раз модифицирует эту переменную.

          • Алексей пишет:

            Viron, спасибо за пояснение.

            По поводу описания работы функции:
            параметры 4 и 5 выходные параметры функции. В 4-ый функция заносит «закодированный цифровой пакет»(дословно из описания функции) а в 5-ый его длину.

            Выяснил, проблема действительно в параметре 4. Параметр 5 после возникновения исключения содержит значение, отличное от нуля, следовательно функция отрабатывает, пакет формирует, вычисляет его длину и заносит в 5-ый параметр и корректно маршалится.

            Теперь, что может быть не так с параметром 4.
            Я ранее с С++ особо не пересекался, может вы мне поясните. Описание типа в коде библиотеки на с++: typedef char* SsfCharstring;
            Что значит это описание?

            Я попытался использовать StringBuffer так:
            //Описание маршалинга функции
            public static extern bool SL_SIGN(
            [In, MarshalAs(UnmanagedType.LPStr)] string inId, //char* inId,
            [In, MarshalAs(UnmanagedType.LPStr)] string inMsg, //char* inMsg,
            [In, MarshalAs(UnmanagedType.I4)] int inMsgLen, //int inMsgLen,
            [In, MarshalAs(UnmanagedType.Bool)] bool inMsgDetached,
            [Out, MarshalAs(UnmanagedType.LPStr, SizeParamIndex = 5)] out StringBuilder outSign, //SsfCharstring *outSign,
            [In, Out, MarshalAs(UnmanagedType.I4)] ref int outSignLen //int *outSignLen
            );

            //Использование в коде
            StringBuilder signedMsg = new StringBuilder();
            int signedMsgLen=0;
            bool result = SL_SIGN(«310006», «test», 4, false, out signedMsg, ref signedMsgLen);

            //Получаю ошибку
            Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

            Так же попытался использовать char[] как принимающий тип:
            //Получаю ошибку, которая связанна с не корректным указанием неуправляемого типа. Какой тип должен быть?

            //Описание маршалинга функции
            public static extern bool SL_SIGN(
            [In, MarshalAs(UnmanagedType.LPStr)] string inId, //char* inId,
            [In, MarshalAs(UnmanagedType.LPStr)] string inMsg, //char* inMsg,
            [In, MarshalAs(UnmanagedType.I4)] int inMsgLen, //int inMsgLen,
            [In, MarshalAs(UnmanagedType.Bool)] bool inMsgDetached,
            [Out, MarshalAs(UnmanagedType.LPStr, SizeParamIndex = 5)] out char[] outSign, //SsfCharstring *outSign,
            [In, Out, MarshalAs(UnmanagedType.I4)] ref int outSignLen //int *outSignLen
            );

            //Использование в коде
            char[] signedMsg;
            int signedMsgLen=0;
            bool result = SL_SIGN(«310006», «test», 4, false, out signedMsg, ref signedMsgLen);

            Cannot marshal ‘parameter #5’: Invalid managed/unmanaged type combination (Arrays can only be marshaled as LPArray, ByValArray, or SafeArray).

            Сейчас пытаюсь найти описание в интернете как типы данных соотносятся. Буду признателен за ответ. Если вы проживаете в Киеве, с меня пиво. Спасибо.

          • viron пишет:

            Нет, я из России :). Предварительно выделите в StringBuilder’e достаточное количество символов, которое требует функция, раз она игнорирует нулевой размер в пятом параметре. Только вы можете разобраться в этих проблемах — у вас ведь есть компилятор и вы работаете над проектом. Я лишь даю подсказки.

            В конце концов, вы можете выделить буфер для 4-го параметра с помощью Marshal.AllocHGlobal. А параметр переделать так:
            [In, MarshalAs(UnmanagedType.SysInt)] IntPtr outSign, //SsfCharstring *outSign,
            Затем преобразовать это в строку, и освободить память. Хотя как по мне, возня с указателями в C# это не очень красивое решение.

            typedef char* SsfCharstring;
            это определение нового типа, обычный способ представления однобайтных строк в Си.

  4. Алексей пишет:

    Да и забыл еще первые четыре параметра IN, два последних Out.

  5. Максим пишет:

    Помогите вызвать функцию типа long Nown::Create(char const*, char const*, void**, long) из c#?

Добавить комментарий

Ваш e-mail не будет опубликован.