Советы по использованию библиотеки C из C #

У меня есть библиотека на C, которую я хотел бы использовать из C #.

Из того, что я почерпнул из интернета, одна идея это обернуть его в C ++ DLL, и DllImport это.

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

int lmdif(int (*fcn)(int, int, double*, double*, int*), 
          int m, int n, double* x, double ftol, double xtol, double gtol, 
          int maxfev, double epsfcn, double factor)

Приведите такой интерфейс, какие неприятности я должен искать? (И решения тоже, пожалуйста)

Почему ты не ...

- переписать на C #? Я начал, но это уже машинный перевод с Фортрана, и мне не очень нравятся вещи, которые я не могу понят

- Используйте существующая библиотека .NET? Я пытаюсь прямо сейчас, но результаты не совсем то же само

- Перекомпилировать в C ++? Я думаю об этом, но это похоже на большую бол

 leppie01 июн. 2012 г., 11:04
Attack === использовать?
 Prof. Falken01 июн. 2012 г., 11:21
Интересный вопрос, кстати! Есть много способов взаимодействия с библиотеками Си. На вашем месте я бы, возможно, сделал бы сетевой интерфейс между C-программой, выполняющей библиотеку, и вашей .NET-программой. Но это будет зависеть, например, от требований к производительности. Я никогда не слышал о Reflection.emit, круто знать. @ Leppie
 Benjol01 июн. 2012 г., 22:50
@ Тотонга, это на самом делебуквальн a .lib У меня сложилось впечатление, что я не могу достичь этого напрямую из C #.
 Totonga02 июн. 2012 г., 06:52
@ Benjol, верно, если это библиотека без ассоциированной DLL, вы не можете получить к ней доступ. Но вы можете связать библиотеку в dll, используя файл def для экспорта необходимых методов без перекомпиляции кода.
 Totonga01 июн. 2012 г., 20:12
Насколько я знаю, в Fortran DLL есть интерфейс C, и он должен быть доступен без оболочки Cpp. Но я не уверен, может ли указатель функции работать.

Ответы на вопрос(6)

через StringBuilder. Просто выведите свои числа в StringBuilder, устанавливая разделители в соответствующих позициях и так далее:

class Program
    {  
        [DllImport("namEm.DLL", CallingConvention = CallingConvention.Cdecl, EntryPoint = "nameEm", 
            CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
        public static extern int nameEm([MarshalAs(UnmanagedType.LPStr)] StringBuilder str);
        static void Main(string[] args)
        {
            int m = 3;
            StringBuilder str = new StringBuilder();
            str.Append(String.Format("{0};", m));
            str.Append(String.Format("{0} {1:E4};", 5, 76.334E-3 ));
            str.Append(String.Format("{0} {1} {2} {3};", 65,45,23,12));
            m = nameEm(str);
        }
    }

А на стороне С, возьми StringBuilder как символ *:

extern "C"
{
    __declspec(dllexport) int __cdecl nameEm(char* names)
    {
        int n1, n2, n3[4];
        char *token,
             *next_token2 = NULL,
             *next_token = NULL;
        float f;

        sscanf_s(strtok_s(names, ";", &next_token), "%d", &n2);
        sscanf_s(strtok_s(NULL, ";", &next_token), "%d %f", &n1, &f);
        // Observe the use of two different strtok-delimiters.
        // the ';' to pick the sequence of 4 integers,
        // and the space to split that same sequence into 4 integers.
        token = strtok_s(strtok_s(NULL, ";", &next_token)," ",&next_token2);
        for (int i=0; i < 4; i++)
        {
             sscanf_s(token,"%d", &n3[i]);
             token = strtok_s(NULL, " ",&next_token2);
        }    
        return 0;
    }
}

  public static unsafe class WrapCDll {
    private const string DllName = "c_functions.dll";

    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
    public static extern void do_stuff_in_c(byte* arg);
  }

Нет необходимости переносить в C ++, и вы получите необходимое соглашение о вызовах.

 Ken White01 июн. 2012 г., 20:03
Большинство библиотек C, предназначенных для работы в Windows, как API-вызовы, используютstdcall соглашение о вызове, а неcdecl.

MSDN статья лет назад, которые привели к InteropSignatureToolkit. Этот маленький инструмент все еще полезен для интерфейсов маршалла C. Скопируйте и перенесите код интерфейса в «SigImp Translation Sniplet» и просмотрите результаты.

Результат следующий, но я понятия не имею, как используется делегат или работает ли он. Так что, если это работает, добавьте несколько комментариев.

/// Return Type: int
///param0: int
///param1: int
///param2: double*
///param3: double*
///param4: int*
public delegate int Anonymous_83fd32fd_91ee_45ce_b7e9_b7d886d2eb38(int param0, int param1, ref double param2, ref double param3, ref int param4);

public partial class NativeMethods {

    /// Return Type: int
    ///fcn: Anonymous_83fd32fd_91ee_45ce_b7e9_b7d886d2eb38
    ///m: int
    ///n: int
    ///x: double*
    ///ftol: double
    ///xtol: double
    ///gtol: double
    ///maxfev: int
    ///epsfcn: double
    ///factor: double
    [System.Runtime.InteropServices.DllImportAttribute("<Unknown>", EntryPoint="lmdif", CallingConvention=System.Runtime.InteropServices.CallingConvention.Cdecl)]
public static extern  int lmdif(Anonymous_83fd32fd_91ee_45ce_b7e9_b7d886d2eb38 fcn, int m, int n, ref double x, double ftol, double xtol, double gtol, int maxfev, double epsfcn, double factor) ;

}

к счастью, .NET справляется с ними довольно хорошо через делегатов.

Единственная проблема с соглашением о вызовах. В C # он испускает только один тип (iircstdcall), тогда как код C может ожидатьcdecl. Последняя проблема может быть решена на уровне IL, хотя (или с помощьюReflection.Emit).

Вот какой-то код который делает это черезReflection.Emit (это поможет понять, какой атрибут psuedo должен быть помещен в делегатInvoke метод).

 TomTom01 июн. 2012 г., 11:39
Скажи, что укажи на условности;)

я получил большую часть пути к тому, чтобы заставить это работать, а затем закончил тем, что вставил соответствующие файлы C в проект C ++ (потому что я боролся с проблемами совместимости в коде, который мне даже не нужен).

Вот несколько советов, которые я подобрал по пути, которые могут быть полезны для людей, которые встречаются по этому вопросу:

Маршаллинг

В Си нет разницы между указателем на двойное double*) и массив парных чисел double*). Когда вы приходите, чтобы взаимодействовать, вы должны быть в состоянии устранить неоднозначность. Мне нужно было передать массивыdouble, так что подпись может выглядеть так:

[DllImport(@"PathToDll")]
public static extern Foo(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)[In] double[] x, 
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)[Out] double[] y, 
    int x_size, 
    int y_size)

ля @C нужна дополнительная информация о длине массива, которую вы должны передать как отдельный параметр. Маршаллинг также должен знать, где этот размер, поэтому вы должны указатьSizeParamIndex, который указывает нулевой индекс параметра размера в списке параметров.

Вы также указываете, в каком направлении должен передаваться массив. В этом примереx передается Foo, который «отправляет обратно»y.

Соглашение о вызовах

Вам на самом деле не нужно понимать более мелкие детали того, что это значит (другими словами, я не знаю), вам просто нужно знать, что существуют разные соглашения о вызовах и что они должны совпадать с обеих сторон. C # по умолчанию являетсяStdCall, C по умолчаниюCdecl. Это означает, что вам нужно явно указывать соглашение о вызовах только в том случае, если оно отличается от значения по умолчанию, на какой бы стороне вы его не использовали.

Это особенно опасно в случае обратного вызова. Если мы передаем обратный вызовC отC#, мы собираемся вызвать этот обратный вызов с помощьюStdCall, но когда мы передаем его, мы используемCdecl. Это приводит к следующим подписям (см.этот вопро для контекста):

//=======C-code======
//type signature of callback function
typedef int (__stdcall *FuncCallBack)(int, int);

void SetWrappedCallback(FuncCallBack); //here default = __cdecl

//======C# code======
public delegate int FuncCallBack(int a, int b);   // here default = StdCall 

[DllImport(@"PathToDll", CallingConvention = CallingConvention.Cdecl)]
private static extern void SetWrappedCallback(FuncCallBack func);
Упаковка обратного вызова

Очевидно, но для меня это было не сразу очевидно:

int MyFunc(int a, int b)
{
   return a * b;
}
//...

FuncCallBack ptr = new FuncCallBack(MyFunc);
SetWrappedCallback(ptr);
.def file

Любые функции, которые вы хотите выставить из проекта C ++ (бытьDllImported), нужно фигурировать вModDef.def file, содержимое которого будет выглядеть примерно так:

LIBRARY MyLibName
EXPORTS
    SetWrappedCallback  @1
extern "C"

Если вы хотите использовать функции C из C ++, вы должны объявить их какextern "C". Если вы включаете заголовочный файл функций C, вы идете так:

extern "C" {
  #include "C_declarations.h"
}
Предварительно скомпилированные заголовки

Еще одна вещь, которую я должен был сделать, чтобы избежать ошибок компиляции, былаRight-click -> Properties -> C/C++ -> Precompiled Headers и установитьPrecompiled header в Не использовать предварительно скомпилированные заголовки для каждого файла 'C'.

Marshal.GetFunctionPointerForDelegate здесь:

"Преобразовывает делегат в указатель на функцию, который вызывается из неуправляемого кода."

Ваш ответ на вопрос