Как я могу получить доступ к неуправляемым глобальным переменным из С#?

Я пишу оболочку .NET для неуправляемой DLL. Исходная DLL — это C++ с оболочкой C, которая в основном просто копирует API в форме C, так что привязка к языку не такая уж проблема. Я уже написал для него привязку к Python, поэтому знаю, что то, что я пытаюсь сделать, должно работать.

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

__declspec(dllexport) extern int (*some_callback)(int32_t*, uint32_t);

Мне нужно прикрепить управляемую функцию к этому указателю, но я не могу понять, как получить нефункциональные ресурсы в неуправляемой библиотеке. Если я не слепой, DllImport импортирует только функции.

Есть ли способ С# сделать это, или мне нужно написать небольшую прокладку DLL на C, которая обеспечивает функции регистрации? Я ненавижу такой подход, потому что он просто кажется неэлегантным, но если я должен, я должен.


person Lee Crabtree    schedule 02.09.2010    source источник
comment
Вы уже рассматривали возможность использования управляемого C++ для создания оболочки?   -  person Dirk Vollmar    schedule 02.09.2010
comment
Я сделал это, но поскольку я хочу иметь возможность работать как под Windows, так и под Linux (Mono), я не могу использовать C++/CLI, поскольку GCC не выводит его, а библиотеки DLL смешанного режима не переносимы.   -  person Lee Crabtree    schedule 03.09.2010


Ответы (1)


Вы правы, P/Invoke не может обрабатывать экспорт данных из DLL. Однако это технически возможно, если получить экспорт напрямую. Это очень некрасиво, и вы, вероятно, когда-нибудь пожалеете об этом, но это сработало:

Образец DLL C/C++:

#include "stdafx.h"

typedef int (__stdcall * pfnCallback)(int*, unsigned*);

extern "C" __declspec(dllexport) 
pfnCallback some_callback;

pfnCallback some_callback;

static int theCallback(int*, unsigned*) {
    return 42;
}

BOOL APIENTRY DllMain( HMODULE hModule, DWORD reason, LPVOID reserved) {
    if (reason == DLL_PROCESS_ATTACH) {
        some_callback = theCallback;
    }
    return TRUE;
}

Тестовый код С#:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

class Program {
    unsafe static void Main(string[] args) {
        IntPtr hMod = LoadLibrary("cpptemp10.dll");
        if (hMod == IntPtr.Zero) throw new Win32Exception();
        IntPtr export = GetProcAddress(hMod, "some_callback");
        if (export == IntPtr.Zero) throw new Win32Exception();
        IntPtr callback = Marshal.ReadIntPtr(export);
        some_callback dlg = (some_callback)Marshal.GetDelegateForFunctionPointer(callback, typeof(some_callback));
        int retval = dlg(null, null);
        Console.WriteLine(retval);
        Console.ReadLine();
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    unsafe delegate int some_callback(int* arg1, uint* arg2);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr LoadLibrary(string path);
    [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
    private static extern IntPtr GetProcAddress(IntPtr hMod, string name);

}
person Hans Passant    schedule 02.09.2010
comment
О Боже. Ты... ты злой. - person Lee Crabtree; 03.09.2010
comment
Я должен признать, что это был не тот ответ, которого я ожидал. Может быть, голосование «это было полезно» было бы более подходящим? Если это не помогло, может быть, что-то не так с вопросом? - person Hans Passant; 03.09.2010
comment
Мне жаль. Я пытался с легким юмором. Ваше решение работает, насколько я могу судить, но похоже на то, о чем позже может пожалеть парень, и я должен задаться вопросом, насколько оно портативно (поскольку я надеюсь заставить вещи работать с Mono как Что ж). - person Lee Crabtree; 03.09.2010
comment
Это не портативно. Можете ли вы даже экспортировать данные из .so в Linux? Просто добавьте экспортированную функцию, которая возвращает указатель. - person Hans Passant; 03.09.2010