在包含线程本地存储(TLS)回调的非托管DLL上使用DllImport时发生异常
本文关键字:DLL DllImport 异常 线程 包含 存储 回调 TLS | 更新日期: 2023-09-27 17:59:39
我有一个.NET应用程序,它加载一个包含多个入口点的非托管DLL(C++)。设置运行良好,我可以使用.NET中入口点返回的信息(即字符串)。但是,只要链接器包含依赖于线程的逻辑(例如_beginthread),就会向DLL中添加.tls部分,该部分可以如果运行dumpbin/exports,则程序抛出异常:Access违规和EntryPointNotFound异常。
作为一个例子,我创建了两个简单的项目来演示我的案例:
- TestApi:C++项目,输出DLL
- ConsoleApplication1:C#.NET项目,该项目加载TestApi.dll并调用导出的dll函数
上面的项目不包括对_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
如果我添加以下片段,它将TLS回调添加到DLL,那么一切都会中断:
#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现在包含一个预期的入口点,但在"摘要"的末尾还有一个.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.
垃圾箱没有显示DLL应该被错误格式化的任何迹象,但尽管如此,我还是得到了上面的异常。当我运行我的原始应用程序时,症状是相同的:
- 如果.tls节可从dumpbin中获得,则程序将崩溃访问违规
- 如果没有可用的.tls部分,则程序运行良好
我错过什么了吗?DllImport是否可能只处理不包含.tls节的DLL?我显而易见的解决方案是重构代码,这样我的入口点就不依赖于线程逻辑,这样链接器就不会将.tls部分添加到DLL中。但是,我仍然想知道为什么会发生这种情况。有什么想法吗?
使用的环境:
- Windows 7 Enterprise SP1
- Visual Studio 2012
- .NET Framework 4
原来/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部分,一切都正常。