проблема преобразования типов для маршалинга типов данных из C # в C ++ - PullRequest
0 голосов
/ 08 января 2019

В настоящее время я работаю над приложением C # (.NET Framework 4.7.2), использующим некоторую бизнес-логику из неуправляемой библиотеки C ++. Я пытаюсь передать данные (взаимодействие) назад и вперед из C # в C ++. Я не могу использовать C ++ / CLI, в моем проекте не разрешена общеязыковая среда выполнения.

Отлично работает для int. К сожалению, как только я пытаюсь отправить другой тип данных, я получаю ошибку преобразования, например float 4.2f становится 1 , а строка «fourtytwo» превращается в -1529101360 .

Мой код C # выглядит так:

// works fine, creates an instance of TestClass    
var test = TestProxy.Wrapper_Create("test"); 

// int, works fine, a = 42
var a = TestProxy.TryInt(test, 42);

// float, problem, b = 1
var b = TestProxy.TryFloat(test, 4.2f);

// string, problem, c = -159101360
var c = TestProxy.TryString(test, "fourtytwo");

Мой класс C # Interop Proxy для вызова собственного (неуправляемого) кода C ++ выглядит следующим образом:

public static class TestProxy
    {
        private const string coreDLL = "test.core.dll";

        [DllImport(coreDLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr Wrapper_Create(string name);

        [DllImport(coreDLL, EntryPoint = "?TryInt@TestClass@@XXX@X", CallingConvention = CallingConvention.ThisCall)]
        public static extern int TryInt(IntPtr instance, int n);

        [DllImport(coreDLL, EntryPoint = "?TryFloat@TestClass@@XXX@X", CallingConvention = CallingConvention.ThisCall)]
        public static extern int TryFloat(IntPtr instance, float n);

        [DllImport(coreDLL, EntryPoint = "?TryString@TestClass@@XXX@X", CallingConvention = CallingConvention.ThisCall)]
        public static extern int TryString(IntPtr instance, string n);

    }

Мой родной (неуправляемый) C ++ выглядит так: заголовочный файл:

#ifdef TESTCORE_EXPORTS
#define TESTCORE_API __declspec(dllexport)
#endif

#pragma once

extern "C"
{
    class TESTCORE_API TestClass
    {
    private:
        char* name;
    public:
        TestClass(char*);
        int TryInt(int);
        float TryFloat(float);
        char* TryString(char*);
    };

    TESTCORE_API TestClass* Wrapper_Create(char* name);
}

файл реализации:

#include "stdafx.h"

#include "TESTCore.h"

TestClass::TestClass(char* n) 
{
    name = n;
}

int TestClass::TryInt(int n) 
{
    return n; // works fine
}

float TestClass::TryFloat(float n)
{
    return n; // something goes wrong here
}

char* TestClass::TryString(char* n)
{
    return n; // something goes wrong here
}

extern "C"
{
    TESTCORE_API TestClass * Wrapper_Create(char* name)
    {
        return new TestClass(name);
    }

    TESTCORE_API int TryInt(TestClass * instance, int n) 
    {
        if (instance != NULL) 
        {
            return instance->TryInt(n);
        }
    }

    TESTCORE_API float TryFloat(TestClass * instance, float n) 
    {
        if (instance != NULL) 
        {
            return instance->TryFloat(n);
        }
    }

    TESTCORE_API char* TryString(TestClass * instance, char* n) 
    {
        if (instance != NULL)
        {
            return instance->TryString(n); 
        }
    }
}

Знаете ли вы, как правильно маршалировать float, строки из C # в C ++ и обратно?

Спасибо!

1 Ответ

0 голосов
/ 08 января 2019

C ++ не имеет стандартного ABI. Редко хорошая идея использовать классы C ++ в разных DLL, даже если у вас одинаковый язык с обеих сторон.

Есть лучшие способы.

  1. Замените ваши __thiscall методы класса глобальными функциями, cdecl или stdcall, в зависимости от того, что вам нравится (но обратите внимание, что C # и C ++ имеют разные значения по умолчанию, если вы ничего не сделаете, C ++ будет использовать cdecl, C # будет импортировать как stdcall) , Вы можете передать указатель «this» класса в первом аргументе, IntPtr в C #, как вы это делаете сейчас. Также, если вы напишите extern "C" или будете использовать файл определения модуля, у них будут понятные для человека имена.

  2. Если вы хотите объекты, используйте COM. Объявите интерфейс, который наследуется от IUnknown, внедрите его в C ++ (я обычно использую ATL) и экспортируйте глобальную функцию для создания экземпляра этого объекта (2 строки в ATL, CComObject<T>::CreateInstance, за которыми следует AddRef). Нет необходимости регистрироваться, библиотеки типов, вам просто нужно реализовать IUnknown (но посмотрите это , если вы хотите использовать их из нескольких потоков)

Обновление: строки действительно сложнее. Примените [MarshalAs(UnmanagedType.LPTStr)] к аргументу. Примените [return: MarshalAs(UnmanagedType.LPTStr)] к функции. Укажите PreserveSig=true в вашем DllImport. Наконец, измените код C ++, чтобы он возвращал копию строки, то есть вызовите strlen, затем CoTaskMemAlloc (не забудьте о '\0'), затем strcpy.

Более простой способ работы со строками выглядит так:

HRESULT TryString( TestClass *instance, BSTR i, BSTR *o )

По крайней мере, есть встроенные классы CComBSTR и _bstr_t для управления памятью.

...