Изменение имени в экспортированной функции-члене C++ в C# (Unity)

Возможный дубликат:
Плагин C++ для Unity "EntryPointNotFoundExeption"

Я понимаю, как предотвратить искажение имен с помощью extern "C" для отдельных функций в C++, но есть ли способ предотвратить это при экспорте функций-членов?

WMIWrapper.cpp

namespace WMIWrapper
{

    extern "C" {

        WMIWrapper::WMIWrapper()
        {
            _locator = NULL;
            _service = NULL;
            _monitors = NULL;
        }

        WMIWrapper::~WMIWrapper()
        {
            if(_service != NULL)
                _service->Release();
            if(_locator != NULL)
                _locator->Release();
        }

        void WMIWrapper::CreateCOM(wchar_t* err, int errLength)
        {
            wstringstream ERRStream (wstringstream::in | wstringstream::out);
            HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);  
            if(FAILED(hRes))  
            {  
                ERRStream << "Unable to launch COM: 0x" << std::hex << hRes << endl; 
            } 

            hRes = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0);
            if(FAILED(hRes))
            {
                ERRStream << "Unable to set security level for COM: " << std::hex << hRes << endl;
            } 

            if(FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&_locator))))  
            {  
                ERRStream << "Unable to create a WbemLocator: " << std::hex << hRes << endl;   
            }

            if(ERRStream != NULL)
                wcscpy_s(err, errLength, ERRStream.str().c_str());
        }

        void WMIWrapper::CreateService(wchar_t* err, int errLength)
        {
            wstringstream ERRStream (wstringstream::in | wstringstream::out);
            HRESULT hRes;
            if(_locator == NULL || FAILED(hRes = _locator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &_service)))  
            {  
                ERRStream << "Unable to connect to \"CIMV2\": " << std::hex << hRes << endl; 
            }  

            if(ERRStream != NULL)
                wcscpy_s(err, errLength, ERRStream.str().c_str());
        }

        void WMIWrapper::GetMonitors(wchar_t* err, int errLength)
        {
            HRESULT hRes;
            wstringstream ssMonitorDescription;
            if(_locator == NULL 
                || _service == NULL
                || FAILED(hRes = _service->ExecQuery(L"WQL", L"SELECT * FROM Win32_DesktopMonitor", WBEM_FLAG_FORWARD_ONLY, NULL, &_monitors)))
            {
                ssMonitorDescription << "Unable to retrieve desktop monitors: " << std::hex << hRes << endl;
                wcscpy_s(err, errLength, ssMonitorDescription.str().c_str());
                return;
            }

            IWbemClassObject* clsObj = NULL;
            int numElems;
            while((hRes = _monitors->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE)
            {
                if(FAILED(hRes))
                    break;

                VARIANT vRet;
                VariantInit(&vRet);
                if(SUCCEEDED(clsObj->Get(L"Description", 0, &vRet, NULL, NULL)) && vRet.vt == VT_BSTR)
                {
                    ssMonitorDescription << "Description: " << vRet.bstrVal << endl;
                    VariantClear(&vRet);
                }
            }

            clsObj->Release();

            wcscpy_s(err, errLength, ssMonitorDescription.str().c_str());
        }

        void WMIWrapper::HelloWorld(wchar_t* testString, int length)
        {
            wstring hello = L"Hello World";
            wcscpy_s(testString, length, hello.c_str());
        }
    }
}

WMIWrapper.h

#ifndef _WMIWRAPPER_H_
#define _WMIWRAPPER_H_

#include <Windows.h>  
#include <sstream>  
#include <iostream>
#include <WbemCli.h>  

using std::endl;
using std::wstring;
using std::wstringstream;

#pragma comment(lib, "wbemuuid.lib")  

namespace WMIWrapper
{
    extern "C" {

        class WMIWrapper 
        {  
        public:
            WMIWrapper();
            ~WMIWrapper();


            __declspec(dllexport) void CreateCOM(wchar_t*, int);
            __declspec(dllexport) void CreateService(wchar_t*, int);
            __declspec(dllexport) void GetMonitors(wchar_t*, int);
            __declspec(dllexport) void HelloWorld(wchar_t*, int);


        private:
            IWbemLocator* _locator;
            IWbemServices* _service;
            IEnumWbemClassObject* _monitors;
        };
    }
}

#endif

Теперь, когда я хочу использовать эти функции в Unity, мне нужно декомпилировать dll, чтобы узнать, какие у меня EntryPoints для имен функций. Я не хочу этого делать.

Я знаю, что немного переусердствовал с внешним "C"...

1 ответ

Решение

ОБНОВЛЕНИЕ: Как @peechykeen указал в комментариях, если вы собираетесь использовать.def, то вы можете напрямую переименовать ваши искаженные имена. Я оставляю оригинальный ответ, хотя гораздо полезнее скрывать экспортированные имена, а не переименовывать их.

Оригинальный ответ:

Один из способов сделать это - "спрятать" экспортированные имена за порядковыми номерами. Для этого вам нужно определить файл.def, а затем в его разделе EXPORTS поместить все экспортированные имена, которые вы хотите скрыть. Например, чтобы присвоить порядковый номер 1 функции, экспортируемой сериализацией Boost, вы должны сделать это:

EXPORTS
??0?$oserializer@Vportable_binary_oarchive@@U?$pair@$$CBHH@std@@@detail@archive@boost@@QEAA@XZ  @1  NONAME

И так далее для всех функций. Теперь делать это вручную утомительно и подвержено ошибкам. И вы будете получать ошибки ссылки каждый раз, когда вы меняете какую-либо часть экспортируемого интерфейса. Для полуавтоматизации я использую Dependency Walker и Perl-скрипт. Это работает так:

  1. В файле.def разместите маркеры в разделе ЭКСПОРТ:

    EXPORTS
    ;BEGIN_RENAMING_TAG
    ;END_RENAMING_TAG
    
  2. Загрузите бинарный файл в Dependency Walker, перейдите в окно экспортированных функций, выберите все экспортированные функции и скопируйте их в буфер обмена.

  3. Вставьте скопированный текст между тегами BEGIN/END в файле.def.
  4. Запустите следующий Perl-скрипт для файла.def:

    #perl -w
    print $ARGC;
    die "ERROR: Provide name of one DEF file to process\n" if @ARGV != 1;
    
    my $renaming = 0;
    my $counter = 1;
    my $fileName = $ARGV[0];
    my @lines;
    open(FILE, $fileName) or die $!;
    while(<FILE>)
    {
        if(/;END_RENAMING_TAG/)
        {
            $renaming = 0;
        }
    
        if($renaming == 1)
        {
            chomp;
            my $line = $_."\t@".$counter."\tNONAME";
            push(@lines, $line);
            ++$counter;
        }
        else
        {
            chomp;
            push(@lines, $_);
        }
    
        if(/;BEGIN_RENAMING_TAG/)
        {
            $renaming = 1;
        }
    }
    
    close FILE;
    open(FILE, ">$fileName") or die $!;
    print FILE join("\n", @lines);
    close FILE;
    
  5. Записи в вашем файле.def теперь все в <entry> @<ordinal> NONAME формат.

Теперь я не использую эти порядковые номера для доступа к функциям, я просто переименовываю их, так как это мешает мне открывать сотни экспортируемых функций, которые мне не нужны (для повышения сериализации, которую я использую, в свою очередь, используются dllexport заставить связать его функции). Так что вам нужно будет сделать больше в скрипте Perl и сказать экспортировать перечисление с именами функций и порядковыми номерами. Если вы хотите сделать это правильно, вам потребуется реализовать алгоритм разборки в скрипте Perl, который даст вам точные имена, позаботится о перегрузке (одно и то же имя, разные аргументы) и сохранит константы имен перечислений.

Для доступа к функциям, стоящим за порядковыми номерами, вы используете GetProcAddress но приведите порядковый номер к LPCSTR. Смотрите документацию для функции для более подробной информации.

Другие вопросы по тегам