Вызов родного кода с рукописной сборкой - PullRequest
3 голосов
/ 23 октября 2011

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

Исходный источник DLL следующий:

#define DERM_SIMD_EXPORT        __declspec(dllexport)

#define DERM_SIMD_API           __cdecl

extern "C" {

    DERM_SIMD_EXPORT void DERM_SIMD_API Matrix4x4_Multiply_SSE(float *result, float *left, float *right);

}

void DERM_SIMD_API Matrix4x4_Multiply_SSE(float *result, float *left, float *right) {
    __asm {
       ....
    }
}

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

public unsafe class Simd
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void MatrixMultiplyDelegate(float* result, float* left, float* right);

    public static MatrixMultiplyDelegate MatrixMultiply;

    public static void LoadSimdExtensions()
    {
        string assemblyPath = "Derm.Simd.dll";

        IntPtr address = GetProcAddress.GetAddress(assemblyPath, "Matrix4x4_Multiply_SSE");

        if (address != IntPtr.Zero) {
            MatrixMultiply = (MatrixMultiplyDelegate)Marshal.GetDelegateForFunctionPointer(address, typeof(MatrixMultiplyDelegate));
        }
    }
}

Используя приведенные выше источники, код работает без ошибок (указатель на функцию получен, а делегат фактически создан.

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

Фактическая проблема заключается в реализации функции: она содержит asm блок с инструкциями SSE; если я удаляю блок asm , код работает отлично.

Я подозреваю, что мне не хватает некоторой сборки для сохранения / восстановления реестра, но я совершенно не осведомлен с этой стороны.

Странно то, что если я изменю соглашение о вызовах на __stdcall, отладочная версия, похоже, будет работать, а выпускная версия будет вести себя так, как если бы использовалась __cdecl.

(И только потому, что мы здесь, вы можете уточнить, имеет ли значение конвекция вызова?)


Хорошо, спасибо за комментарий David Heffernan Я обнаружил, что плохие инструкции, вызывающие проблему, следующие:

 movups result[ 0], xmm4;
 movups result[16], xmm5;

movups инструкции перемещают 16 байтов в (не выровненную) память.

Функция вызывается с помощью следующего кода:

 unsafe {
    float* prodFix = (float*)prod.MatrixBuffer.AlignedBuffer.ToPointer();
    float* m1Fix = (float*)m2.MatrixBuffer.AlignedBuffer.ToPointer();
    float* m2Fix = (float*)m1.MatrixBuffer.AlignedBuffer.ToPointer();

    if (Simd.Simd.MatrixMultiply == null) {
                    // ... unsafe C# code
    } else {
        Simd.Simd.MatrixMultiply(prodFix, m1Fix, m2Fix);
    }
}

Где MatrixBuffer - мой класс; его член AlignedBuffer распределяется следующим образом:

// Allocate unmanaged buffer
mUnmanagedBuffer = Marshal.AllocHGlobal(new IntPtr((long)(size + alignment - 1)));

// Align buffer pointer
long misalignment = mUnmanagedBuffer.ToInt64() % alignment;
if (misalignment != 0)
    mAlignedBuffer = new IntPtr(mUnmanagedBuffer.ToInt64() + misalignment);
else
    mAlignedBuffer = mUnmanagedBuffer;

Может быть, ошибка вызвана Marshal.AllocHGlobal или IntPtr черная магия?


Это минимальный источник для определения ошибки:

void Matrix4x4_Multiply_SSE(float *result, float *left, float *right)
{
    __asm {
        movups xmm0,    right[ 0];

        movups result, xmm0;
    }
}


int main(int argc, char *argv[])
{
    float r0[16];
    float m1[16], m2[16];

    m1[ 0] = 1.0f; m1[ 4] = 0.0f; m1[ 8] = 0.0f; m1[12] = 0.0f;
    m1[ 1] = 0.0f; m1[ 5] = 1.0f; m1[ 9] = 0.0f; m1[13] = 0.0f;
    m1[ 2] = 0.0f; m1[ 6] = 0.0f; m1[10] = 1.0f; m1[14] = 0.0f;
    m1[ 3] = 0.0f; m1[ 7] = 0.0f; m1[11] = 0.0f; m1[15] = 1.0f;

    m2[ 0] = 1.0f; m2[ 4] = 0.0f; m2[ 8] = 0.0f; m2[12] = 0.0f;
    m2[ 1] = 0.0f; m2[ 5] = 1.0f; m2[ 9] = 0.0f; m2[13] = 0.0f;
    m2[ 2] = 0.0f; m2[ 6] = 0.0f; m2[10] = 1.0f; m2[14] = 0.0f;
    m2[ 3] = 0.0f; m2[ 7] = 0.0f; m2[11] = 0.0f; m2[15] = 1.0f;

    r0[ 0] = 0.0f; r0[ 4] = 0.0f; r0[ 8] = 0.0f; r0[12] = 0.0f;
    r0[ 1] = 0.0f; r0[ 5] = 0.0f; r0[ 9] = 0.0f; r0[13] = 0.0f;
    r0[ 2] = 0.0f; r0[ 6] = 0.0f; r0[10] = 0.0f; r0[14] = 0.0f;
    r0[ 3] = 0.0f; r0[ 7] = 0.0f; r0[11] = 0.0f; r0[15] = 0.0f;

    Matrix4x4_Multiply_SSE(r0, m1, m2);
    Matrix4x4_Multiply_SSE(r0, m1, m2);

    return (0);
}

Практически после второго movups стек изменяет значение result (хранится в стеке) и сохраняет значения xmm0 в измененном ( и неверно) адрес хранится в результате .

После выхода из * Matrix4x4_Multiply_SSE * исходная память не изменяется.

Чего мне не хватает?

Ответы [ 3 ]

2 голосов
/ 23 октября 2011

Неправильная коррекция выравнивания.Вам нужно добавить alignment-misalignment, чтобы исправить выравнивание.Поэтому код должен выглядеть следующим образом:

mAlignedBuffer = 
    new IntPtr(mUnmanagedBuffer.ToInt64() + alignment - misalignment);

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

1 голос
/ 25 октября 2011

Ваша сборка была ошибочной. Есть разница между

void DoSomething(int *x)
{
    __asm
    {
        mov x[0], 10   // wrong
            mov [x], 10    // also wrong
        mov esi,x      // first get address
        mov [esi],500  // then assign - correct
    }
}

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

int x=0;
DoSomething(&x);

С помощью mov [x], 10 вы не устанавливаете x на 10, а записываете в свой стек.

0 голосов
/ 24 октября 2011

Я найду решение.Загрузка значения указателя в регистр ЦП и использование регистра для перенаправления в память:

mov esi, result;
movups [esi][ 0], xmm0;

Использование этих инструкций делает код работающим должным образом.


Но вопрос остается нерешенным полностьюпоскольку инструкция movups может принимать в качестве первого аргумента адрес памяти;так что, если кто-то знает, что происходит, я рад проверить лучший ответ.

...