我正在构建一个非常简单的win32控制台应用程序,它使用C编码并使用Visual Studio 2010/2013进行编译。它将有一个消息队列在与主节点不同的线程中运行。控制台将从VBA启动,然后通过API函数PostThreadMessage
发送用户定义的消息。
我找到了一种基于它的进程ID获取控制台的所有线程ID的方法。但是,我还没有找到一种方法来识别正确的方法。我现在正在做的是向所有控制台线程发送消息。在该消息中,我通过VarPtr
函数将指向我的一个VBA变量的指针作为LPARAM参数传递。
线程正在接收消息但是当应用程序尝试将id写入VBA变量时,应用程序正在崩溃。我知道可以将VBA变量指针传递给DLL函数。我过去使用API函数EnumWindows
使用过这种方法。但是,我对C编程完全不熟悉,也无法弄清楚如何在我的控制台代码中做同样的事情。
以下是我为解决此问题所采取的步骤:
在Shell.c中添加了以下代码
#include <stdio.h>
#include <Windows.h>
#define WM_GET_ID WM_USER + 1
DWORD WINAPI MessageQueue(LPVOID n) {
MSG msg;
int *pVar;
BOOL MsgReturn;
DWORD dwThreadId = GetCurrentThreadId();
while (1) {
MsgReturn = GetMessage(&msg, NULL, WM_GET_ID, WM_GET_ID);
if (MsgReturn) {
switch (msg.message) {
case WM_GET_ID:
pVar = (int*)msg.lParam;
// Access violation writing location 0x0026F470
*pVar = (int)dwThreadId;
}
}
}
return 0;
}
int main() {
CreateThread(NULL, 0, MessageQueue, NULL, 0, NULL);
// Pause the console window to keep thread running
getchar();
return 0;
}
将代码编译为ConsoleTest.exe
将以下代码添加到PointerTest模块
Option Explicit
Private Type THREADENTRY32
dwSize As Long
cntUsage As Long
th32ThreadID As Long
th32OwnerProcessID As Long
tpBasePri As Long
tpDeltaPri As Long
dwFlags As Long
End Type
Private Const WM_USER As Long = &H400
Private Const WM_GET_ID As Long = WM_USER + 1
Private Const WM_TERMINATE As Long = WM_USER + 2
Private Const TH32CS_SNAPPROCESS As Long = &H2
Private Const TH32CS_SNAPTHREAD As Long = &H4
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function CreateToolhelp32Snapshot Lib "kernel32" (ByVal dwFlags As Long, ByVal th32ProcessID As Long) As Long
Private Declare Function PostThreadMessage Lib "user32" Alias "PostThreadMessageA" (ByVal idThread As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Boolean
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare Function Thread32First Lib "kernel32" (ByVal hSnapshot As Long, ByRef lpte As THREADENTRY32) As Boolean
Private Declare Function Thread32Next Lib "kernel32" (ByVal hSnapshot As Long, ByRef lpte As THREADENTRY32) As Boolean
Private Sub SendConsoleMsg()
Dim PID As Long
Dim tHandle As Long
Dim dwThreadId As Long
Dim PName As String
Dim Success As Boolean
Dim tEntry As THREADENTRY32
PName = "C:\My Stuff\Visual Studio" & _
"\ConsoleTest\Debug\ConsoleTest.exe"
PID = Shell(PName, vbNormalFocus)
Sleep 250
tHandle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0&)
If tHandle = -1 Then
Debug.Print "Could not obtain thread handle"
Exit Sub
End If
tEntry.dwSize = Len(tEntry)
Success = Thread32First(tHandle, tEntry)
If Success Then
Do
If tEntry.th32OwnerProcessID = PID Then
PostThreadMessage tEntry.th32ThreadID, _
WM_GET_ID, 0, VarPtr(dwThreadId)
End If
Loop While Thread32Next(tHandle, tEntry)
End If
CloseHandle tHandle
Sleep 10
Debug.Print dwThreadId
End Sub
当我在VBA中运行SendConsoleMsg
过程时,控制台会在几秒钟内加载然后崩溃。当我进入调试模式时,我在C代码示例中看到了访问冲突。请让我知道我需要做些什么才能使其正常工作。我已经搜索了几天的解决方案,但仍然干了。
我能够使用code example Frankie_C链接选项1来使其正常工作。但是,我最终将DLL函数调用约定从__declspec(dllexport)
更改为__stdcall
并包含了函数导出的DEF文件。另外,我在DLL中添加了另一个函数,因此我可以将VBA中的线程ID作为DWORD返回 -
DWORD __stdcall GetConsoleThreadId() {
WCHAR Buf[5];
GetSharedMem(Buf, 5);
SetSharedMem(TEXT("0"));
return (DWORD)wcstod(Buf, '\0');
}
在我的MessageQueue
函数中,我添加了以下代码以将线程id加载到共享内存中 -
WCHAR Buf[5];
HMODULE hDLL;
FARPROC SetSharedMem;
hDLL = LoadLibrary(TEXT("C:\\My Stuff\\Visual Studio\\SharedMem\\Debug\\SharedMem.dll"));
if (hDLL != NULL) {
SetSharedMem = GetProcAddress(hDLL, "SetSharedMem");
if (SetSharedMem) {
swprintf_s(Buf, 5, L"%d", GetCurrentThreadId());
SetSharedMem(Buf);
}
FreeLibrary(hDLL);
}
答案 0 :(得分:1)
首先,不同的进程在彼此之间隔离了不同的内存空间。这是现在操作系统的主要特征之一:处理私有内存。
如果要从运行2个或更多不同进程的代码访问共享内存,则无法使用GlobalAlloc()。此功能来自16位Windows OS中本地和全局进程内存之间的旧区别。因此,承认其全球名称并不意味着可以从不同的流程中获取。
要制作技巧,你需要基本的2种方法:
在第一种情况下,您只需在第一个进程中创建一个内存映射文件,然后在第二个进程中打开该文件,然后操作共享内存。请参阅this sample或this one。最后一个将映射的文件和服务例程放在需要访问公共数据的所有进程之间共享的DLL中。
对于第二种情况,您必须创建DLL,在DLL内部,您将使用DLL的.def
文件中的SECTION
command创建共享部分。您还可以使用#pragma section
关联共享属性。
在加载dll的任何进程中,共享部分内创建的所有数据都是可见的。
//Use pragma to create a section named 'MySharedSection', readable, writable and shared
#pragma section( "MySharedSection", read, write, shared)
//Allocate the section, all data after this spec will be allocated in the new section
__declspec(allocate("MySharedSection"))
__declspec(dllexport) char CommonBuffer[1024];
在上面的示例中,我创建了一个1024字节的共享缓冲区,可供将加载DLL的所有进程使用。该符号也被导出,使其可以从链接DLL的代码访问。
这个系统已经过时,它从Win3.1开始使用。今天,首选方法是使用CreateFileMapping()