我理解如何在c ++中的单个函数上使用extern“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”有点过分热心......
答案 0 :(得分:3)
更新:正如@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脚本。它的工作原理如下:
在EXPORTS部分的.def文件中放置标记:
EXPORTS
;BEGIN_RENAMING_TAG
;END_RENAMING_TAG
将二进制文件加载到Dependency Walker,转到导出的函数窗口,选择所有导出的函数并将它们复制到剪贴板。
在.def文件中运行以下Perl脚本:
#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;
.def文件中的条目现在都是<entry> @<ordinal> NONAME
格式。
现在,我不使用这些序数来访问函数,我只是重命名它们,因为它让我暴露出数百个我不需要的导出函数(我使用的Boost序列化反过来使用{{1}强制链接其功能)。所以你需要在Perl脚本中做更多的事情并说导出一个带有函数名和序数的枚举。如果你想要做到这一点,你必须在Perl脚本中实现一个demangling算法,它将为你提供准确的名称,处理重载(同名,不同的args)并保持枚举名称常量。
为了访问序数背后的函数,可以使用GetProcAddress
,但将序数转换为LPCSTR。有关详细信息,请参阅该函数的文档。