我有一个.NET应用程序,它加载包含多个入口点的非托管DLL(C ++)。 设置工作正常,我可以使用.NET中的入口点返回的信息(即字符串)。 但是只要链接器包含依赖于线程的逻辑(例如_beginthread),就会将.tls部分添加到DLL中,这可以是 通过运行dumpbin / exports看到,程序抛出异常:访问冲突和EntryPointNotFound异常。
作为一个例子,我创建了两个简单的项目来演示我的案例:
上述项目不包括对_beginthread的调用,而是手动添加了线程局部存储(TLS)回调,这会导致运行时错误。 可以在此处下载示例项目:http://www.tempfiles.net/download/201404/343692/TestApi.html
ConsoleApplication1的C#代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[DllImport("TestApi.dll", EntryPoint = "TestFunction", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern string TestFunction();
static void Main(string[] args)
{
try
{
string dllString = TestFunction();
Console.WriteLine("String from DLL: " + dllString);
}
catch (System.Exception ex)
{
Console.WriteLine("Caught exception: " + ex);
}
}
}
}
TestApi.dll的C ++代码:
#pragma unmanaged
#include <string>
#include <objbase.h>
extern "C" __declspec(dllexport) char* TestFunction()
{
// Return string to managed side
std::string cppString = "This is a string from the unmanaged DLL";
size_t stringSize = strlen(cppString.c_str()) + sizeof(char);
char* returnString = (char*)::CoTaskMemAlloc(stringSize);
strcpy_s(returnString, stringSize, cppString.c_str());
return returnString;
}
DLL包含1个预期的入口点(dumpbin / exports TestApi.dll):
ordinal hint RVA name
1 0 00001010 TestFunction = _TestFunction
Summary
1000 .data
7000 .rdata
1000 .reloc
1000 .rsrc
3000 .text
上面的.NET应用程序按预期工作,打印以下输出:
String from DLL: This is a string from the unmanaged DLL
如果我添加以下代码片段,它会向DLL添加TLS回调,一切都会中断:
#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma comment(linker, "/INCLUDE:_tls_entry")
#pragma data_seg(".CRT$XLB" )
VOID NTAPI MyCallback(PVOID handle, DWORD reason, PVOID resv)
{
}
extern "C" PIMAGE_TLS_CALLBACK tls_entry = MyCallback;
DLL现在按预期包含1个入口点,但在“摘要”末尾包含.tls部分:
ordinal hint RVA name
1 0 00001010 TestFunction = _TestFunction
Summary
1000 .data
7000 .rdata
1000 .reloc
1000 .rsrc
3000 .text
1000 .tls
.NET应用程序现在输出:
Unhandled Exception:
Unhandled Exception:
Segmentation fault
在Debug中运行应用程序时,我得到:
An unhandled exception of type 'System.AccessViolationException' occurred in ConsoleApplication1.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Dumpbin没有表明DLL应该是格式错误的,但是我得到了上面的例外。 当我运行我的原始应用程序时,症状是相同的:
我在这里想念一下吗?是否有可能DllImport只处理不包含.tls部分的DLL? 我明显的解决方案是重构代码,以便我的入口点不依赖于线程逻辑,因此链接器不会将.tls部分添加到DLL。 但是,我仍然想知道为什么会这样。有什么想法吗?
使用的环境:
答案 0 :(得分:0)
事实证明,/ clr标志使用DLL的PE格式做了一些讨厌的事情。如果启用了/ clr标志,则从Python 2.7加载DLL时会得到以下输出:
>>> import ctypes
>>> apidll = ctypes.cdll.LoadLibrary('TestApi.dll')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python27\lib\ctypes\__init__.py", line 443, in LoadLibrary
return self._dlltype(name)
File "C:\Python27\lib\ctypes\__init__.py", line 365, in __init__
self._handle = _dlopen(self._name, mode)
WindowsError: [Error 193] %1 is not a valid Win32 application
禁用/ clr标志会给我:
>>> import ctypes
>>> apidll = ctypes.cdll.LoadLibrary('TestApi.dll')
>>>
我的解决方案(正如@HansPassant在上面的评论中也提到的那样)是禁用/ clr标志,即使DLL包含.tls部分,一切都有效。